Значение Jackson java.util.Date в Map‹String, Object› (де-)сериализация

Рассмотрим это свойство

@JsonProperty
private Map<String, Object> myMap;

Когда содержащееся в нем значение java.util.Date сериализуется сколь угодно долго, оно не будет снова десериализовано в Date, поскольку информация о типе отсутствует в Map<String, Object>. Как обойти проблему? Я прочитал ответы на этот вопрос это было бы обходным путем, но не было бы возможности отличить строки, содержащие даты, от дат, сериализованных как строки на карте. Могу ли я попросить Джексона включить информацию о типе для каждого значения сопоставления, чтобы Джексон мог правильно их десериализовать?


person Steffen Harbich    schedule 15.03.2018    source источник
comment
В JSON нет информации о типе. Используя обычный Object, Джексон не может отличить даты от реальных длинных чисел. Если возможно, вы могли бы создать оболочку для Object, которая содержит информацию о типе java, и использовать ее для десериализации фактического значения или (если это не вариант), возможно, возможно форматирование дат в виде строк ISO-8601? Таким образом, вы можете проверить значение на соответствие ожидаемому шаблону и преобразовать его в дату.   -  person dpr    schedule 21.03.2018
comment
@dpr Да, я собираюсь попробовать добавить к ключу карты что-то вроде "[date]", которое затем будет десериализовано как дата.   -  person Steffen Harbich    schedule 21.03.2018
comment
Возможно, эта ссылка будет вам полезна. документы Джексона   -  person Aliaksei Yatsau    schedule 22.03.2018


Ответы (2)


Реализуйте собственный десериализатор и добавьте аннотацию @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));

}
person dieter    schedule 19.03.2018
comment
Спасибо за ваш ответ, но в моем случае карта состоит из строк, логических значений, чисел и дат. У вас есть идея на этот случай? - person Steffen Harbich; 20.03.2018
comment
@SteffenHarbich в этом случае используйте Map<String, Object>. Я обновил свой ответ. - person dieter; 20.03.2018
comment
Хорошо, я думаю, я попытаюсь написать пару сериализатор/десериализатор, которая добавляет/интерпретирует некоторый суффикс или префикс к ключу карты в зависимости от типа (только в случае даты на данный момент), чтобы это было прозрачно для остальной части моего кода . Я дам вам обратную связь завтра. Спасибо! - person Steffen Harbich; 20.03.2018

Наконец, я придумал это решение. Десериализатор:

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);
}

Ключ карты адаптируется в зависимости от типа данных. Это легко расширяется.

person Steffen Harbich    schedule 23.03.2018