我有以下课程
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);
}};
如果在反序列化之前您已经知道 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 的演示,展示了上面的代码。