Почему ObjectMapper меняет тип даты на длинный - PullRequest
1 голос
/ 21 февраля 2020

Я пытаюсь получить карту от объекта, используя Jackson ObjectMapper:

    ObjectMapper oMapper = ObjectMapperWithDate.getObjectMapper();
    Map<String, Object> map = oMapper.convertValue(obj, Map.class);

У меня проблемы с полями Date, поскольку на карте они становятся объектами Long.

Я добавил де / сериализаторы, как в ObjectMapper меняет Date на String

public class ObjectMapperWithDate {
    @Bean
    public static ObjectMapper getObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        mapper.registerModule(
                new SimpleModule("foo")
                        .addDeserializer(Date.class, new DateDeserializer())
                        .addSerializer(Date.class, new DateSerializer())
        );
        return mapper;
    }
    public static class DateSerializer extends StdScalarSerializer<Date> {
        public DateSerializer() {
            super(Date.class);
        }
        @Override
        public void serialize(Date value, JsonGenerator gen, SerializerProvider provider)
                throws IOException {
            DateFormat formatter = new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);
            String output = formatter.format(value);
            gen.writeString(output);
        }
    }
    public static class DateDeserializer extends StdScalarDeserializer<Date> {
        public DateDeserializer() {
            super(Date.class);
        }
        @Override
        public Date deserialize(JsonParser p, DeserializationContext ctxt)
                throws IOException {
            try {
                DateFormat formatter = new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);
                return formatter.parse(p.getValueAsString());
            } catch (Exception e) {
                return null;
            }
        }
    }
}

Конечно, вызов для преобразователя выглядит немного иначе:

    ObjectMapper oMapper = ObjectMapperWithDate.getObjectMapper();
    Map<String, Object> map = oMapper.convertValue(obj, Map.class);

Теперь объекты Date становятся объектами String на карте. С датами, правильно представленными в них. Но мне нужно, чтобы они оставались , чтобы иметь тип даты. Что интересно, если я поставлю точки останова в десериализаторе, это никогда не будет достигнуто. Таким образом, десериализатор никогда не достигается, я думаю, это потому, что преобразователь после сериализации делает Date String или Long, в зависимости от SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, и никогда не распознает Date во время десериализации.

Как сделать так, чтобы свойства Date оставались объектами Date после сопоставления? Мне нужно, чтобы они были распознаны.

Кстати, свойства BigDecimal превращаются в двойные. Вроде бы похожая проблема, но эти два типа не имеют большого значения для моей дальнейшей работы.

Ответы [ 2 ]

1 голос
/ 21 февраля 2020

Поскольку вы определили тип значений карты как Object, Джексон не выбирает пользовательский десериализатор типа Date и использует десериализатор по умолчанию, где он преобразует все типы в базовые c (например, long, String, LinkedHashMap et c.).

Если бы в вашем объекте было только Date полей, вы могли бы просто изменить 2-й аргумент метода convertValue:

Map<String, Date> map = oMapper.convertValue(obj, new TypeReference<Map<String, Date>>() {});

Но, очевидно, это не ваш случай, поэтому самый простой способ сделать это для объекта с различными типами полей - это изменить тип десериализатора на Object и проанализировать все данные внутри него вручную:

    public static class DateDeserializer extends StdScalarDeserializer<Object> {
        public DateDeserializer() {
            super(Object.class);
        }
        @Override
        public Object deserialize(JsonParser p, DeserializationContext ctxt)
                throws IOException {
            String valueAsString = p.getValueAsString();
            try {
                DateFormat formatter = new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);
                return formatter.parse(valueAsString);
            } catch (Exception e) {
                //you could add extra logic to parse other types
                return valueAsString;
            }
        }
    }

Также, не забудьте заменить 1-й аргумент .addDeserializer на Object.class

Для более экзотических c способов сделать это, пожалуйста, проверьте эту статью: http://robertmarkbramprogrammer.blogspot.com/2018/05/de-serialise-json-string-to-map-with.html

0 голосов
/ 21 февраля 2020

Вы должны использовать POJO для представления своих данных вместо Map. Поскольку Map - это неоднозначные пары ключ-значение, для каждого поля нет информации о типе, и Джексон будет считывать значения, не делая никаких выводов относительно информации о их типе. Моделируя ваши данные в определенной структуре, Джексон будет считывать значения в определенные типы.

public class DataTransferObject
{
   Date date;
   BigDecimal decimal;

   public Date getDate ()
   {
      return date;
   }

   public void setDate ( Date date )
   {
      this.date = date;
   }

   public BigDecimal getDecimal ()
   {
      return decimal;
   }

   public void setDecimal ( BigDecimal decimal )
   {
      this.decimal = decimal;
   }
}
objectMapper.convertValue(obj, DataTransferObject.class);

При таком подходе вы можете использовать функции сериализации Джексона для преобразования дат с использованием строк или целых чисел. Пользовательский (де) сериализатор не требуется.

...