Jackson 反序列化泛型类:未检测到 POJO 时默认为字符串

问题描述 投票:0回答:1

我有以下课程

public class Customer<T>{
    private String name;
    private String store;
    private T details;
}

public class Details{
    private String dob;
    private boolean validated;
}

当 JSON 如下时,反序列化起作用

{
  "name": "John Smith",
  "store": "Walmart",
  "details": {
    "dob": "1900/01/01",
    "validated": true
  }

}

但是当给定的 JSON 响应如下所示时,它会失败。

{
  "name": "John Smith",
  "store": "Walmart",
  "details": [
    "Failed customer does not exist in the system", "Please contact administrator"
  ]
}

Jackson Mapper 有什么办法可以处理这些场景吗?如果

details
作为 POJO 提供,则将其反序列化,否则将
details
反序列化为
String
。我这样定义我的映射器:

private static final ObjectMapper Mapper = new ObjectMapper() {{
    setSerializationInclusion(JsonInclude.Include.NON_NULL);
    setDateFormat(new SimpleDateFormat("MM/dd/yyyy HH:mm:ss z", Locale.US));
    configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}}; 
java json generics jackson
1个回答
3
投票

如果在反序列化之前您已经知道 json 包含字段

details
的内容,您可以简单地读取带有
Customer
的通用
TypeReference
。在这种情况下,您的错误消息将显示为
List<String>
而不是
String
,更多地遵循 json 结构。

//Reading a generic Customer with a Details pojo
Customer<Details> c1 = mapper.readValue(jsonPojo, new TypeReference<Customer<Details>>() {});

//Reading a generic Customer with an array of String error messages
Customer<List<String>> c2 = mapper.readValue(jsonArray, new TypeReference<Customer<List<String>>>() {});

但是,这种解决方案需要提前知道 json 包含什么内容(不可能是这样),或者事先对其进行分析,然后进行适当的反序列化(这可能很乏味)。

因此,为了提供更通用的解决方案,同时保持泛型的灵活性,有必要为

details
字段使用自定义解串器,因为逻辑非常具体,仅在
ObjectMapper
上配置是不行的它。

如果详细信息以 pojo 形式提供,则照此编写,否则将详细信息写为字符串值。

但是,为泛型类型

T
定义自定义反序列化器没有什么意义,因为在实现
Details
String
两种情况的反序列化逻辑后,我们将被迫将结果转换为泛型类型
T 
,阻碍了使用泛型的全部意义,并在编译时失去了类型安全的主要优势。

我们可以做的是在字段

details
的可能数据类型中找到一个共同的类型分母。目前,
details
可以是
Details
的实例,也可以是
String
String
类实现了接口
Serializable
,因此如果类
Details
也实现了它,我们可以使用
Serializable
作为泛型类型
T
的上限,并为
Serializable
定义自定义反序列化器。这种方法意味着代替
T
传递的每种类型都必须是
Serializable
的子类型。这并不是一个真正严格的要求,因为大多数 Java 类已经实现了该接口,而我们的类也可以轻松实现它,而无需提供任何实现。

波霍斯

public class Customer<T extends Serializable> {

    private String name;
    private String store;
    @JsonDeserialize(using = MyDeserializer.class)
    private T details;

    // ... implementation ...
}

public class Details implements Serializable {

    private String dob;
    private boolean validated;
    
    // ... implementation ...
}

自定义解串器

public class MyDeserializer extends StdDeserializer<Serializable> {
    public MyDeserializer() {
        this(null);
    }

    public MyDeserializer(Class<?> c) {
        super(c);
    }

    @Override
    public Serializable deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
        JsonNode node = p.getCodec().readTree(p);
        if (node.isArray()) {
            return StreamSupport.<JsonNode>stream(Spliterators.spliteratorUnknownSize(node.iterator(), Spliterator.ORDERED), false)
                    .map(JsonNode::textValue)
                    .collect(Collectors.joining(" - "));
        }
        return ctxt.readTreeAsValue(node, Details.class);
    }
}

测试主要

public class Main {
    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper() {{
            setSerializationInclusion(JsonInclude.Include.NON_NULL);
            setDateFormat(new SimpleDateFormat("MM/dd/yyyy HH:mm:ss z", Locale.US));
            configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        }};

        String jsonPojo = """
                {
                  "name": "John Smith",
                  "store": "Walmart",
                  "details": {
                    "dob": "1900/01/01",
                    "validated": true
                  }
                }
                """;
        Customer<Serializable> c1 = mapper.readValue(jsonPojo, new TypeReference<Customer<Serializable>>() {});
        System.out.println(c1.getDetails().getClass().getSimpleName()); //Prints Details
        System.out.println(c1);

        String jsonArray = """
                {
                  "name": "John Smith",
                  "store": "Walmart",
                  "details": [
                    "Failed customer does not exist in the system", "Please contact administrator"
                  ]
                }
                """;
        Customer<Serializable> c2 = mapper.readValue(jsonArray, new TypeReference<Customer<Serializable>>() {});
        System.out.println(c2.getDetails().getClass().getSimpleName()); //Prints String
        System.out.println(c2);
    }
}

演示

这里还有一个位于 OneCompiler 的演示,展示了上面的代码。

© www.soinside.com 2019 - 2024. All rights reserved.