在Map<String, Object>的(反)序列化中,如何处理Jackson java.util.Date类型的值?

7
考虑这个属性。
@JsonProperty
private Map<String, Object> myMap;

当包含java.util.Date值的对象被序列化为长整型时,由于Map<String, Object>中没有类型信息,它不会再被反序列化为Date。如何解决这个问题?我看到了这个问题的答案,可以使用一种变通方法,但在map中无法区分包含日期的字符串和日期序列化为字符串的情况。 我能告诉Jackson为每个map值包含类型信息,以便Jackson能够正确地反序列化它们吗?

JSON 中没有类型信息。使用普通的 Object,Jackson 无法区分日期和实际的 long 类型。如果可能的话,您可以创建一个包装器来保存 Java 类型信息,并使用它来反序列化实际值,或者(如果不行)将日期格式化为 ISO-8601 字符串是否可行?这样,您就可以检查该值是否与预期模式匹配并转换为日期。 - dpr
@dpr 是的,我即将尝试在地图键后缀上类似"[date]"的东西,然后将其反序列化为日期。 - Steffen Harbich
也许这个链接对你有用。Jackson文档 - Aliaksei Yatsau
2个回答

5

实现自定义的反序列化器并将注解@JsonDeserialize(using = DateDeserializer.class)添加到您的字段中。

看一个例子:

您的Json-Bean

public class Foo {

    private String            name;

    @JsonProperty
    @JsonDeserialize(using = DateDeserializer.class)
    private Map<String, Object> dates;

    [...] // getter, setter, equals, hashcode
}

反序列化器:

public class DateDeserializer extends JsonDeserializer<Map<String, Object>> {

    private TypeReference<HashMap<String, Object>> typeRef = new TypeReference<HashMap<String, Object>>() {};

    @Override
    public Map<String, Object> deserialize(JsonParser p, DeserializationContext ctxt, Map<String, Object> target) throws IOException, JsonProcessingException {

        Map<String, Long> map = new ObjectMapper().readValue(p, typeRef);

        for(Entry<String, Long> e : map.entrySet()){

            Long value = e.getValue();
            String key = e.getKey();

            if(value instanceof Long){ // or if("date".equals(key)) ...
                target.put(key, new Date(value));
            } else {
                target.put(key, value); // leave as is
            }

        }

        return target;
    }

    @Override
    public Map<String, Object> deserialize(JsonParser paramJsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        return this.deserialize(paramJsonParser, ctxt, new HashMap<>());
    }

}

简单测试:

public static void main(String[] args) throws Exception {

    Foo foo1 = new Foo();
    foo1.setName("foo");
    foo1.setData(new HashMap<String, Object>(){{
        put("date",   new Date());
        put("bool",   true);
        put("string", "yeah");
    }});
    ObjectMapper mapper = new ObjectMapper();
    String jsonStr = mapper.writeValueAsString(foo1);
    System.out.println(jsonStr);
    Foo foo2 = mapper.readValue(jsonStr, Foo.class);

    System.out.println(foo2.equals(foo1));

}

谢谢您的回答,但在我的情况下,地图由字符串、布尔值、数字和日期组成。您有什么想法吗? - Steffen Harbich
@SteffenHarbich 在这种情况下使用 Map<String, Object>。我已经更新了我的答案。 - dieter
1
好的,我想我会尝试编写一个序列化器/反序列化器对,根据类型(目前仅在日期的情况下)向映射键添加/解释一些后缀或前缀,以便对我的其余代码透明。明天我会给你反馈。到目前为止感谢您! - Steffen Harbich

1
最终,我想出了这个解决方案。反序列化器:
private TypeReference<Map<String, Object>> typeRef = new TypeReference<Map<String, Object>>() {
};

@Override
public Map<String, Object> deserialize(JsonParser p, DeserializationContext ctxt, Map<String, Object> target) throws IOException {
    Map<String, Object> map = new ObjectMapper().readValue(p, typeRef);

    for (Map.Entry<String, Object> e : map.entrySet()) {
        if (e.getKey().endsWith("[date]")) {
            target.put(e.getKey().substring(0, e.getKey().length() - 6), new Date((Long) e.getValue()));
        }
        else {
            target.put(e.getKey(), e.getValue());
        }
    }

    return target;
}

序列化器:

@Override
public void serialize(Map<String, Object> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
    Map<String, Object> adaptedValue = new HashMap<>(value);

    for (Map.Entry<String, Object> e : value.entrySet()) {
        if (e.getValue() instanceof Date) {
            adaptedValue.put(e.getKey() + "[date]", ((Date) e.getValue()).getTime());
            adaptedValue.remove(e.getKey());
        }
    }

    new ObjectMapper().writeValue(gen, adaptedValue);
}

地图键会根据数据类型进行调整,这很容易扩展。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接