3

I am trying to get a map from the object using Jackson ObjectMapper:

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

I have problems with Date fields, for in the map they are becoming Long objects.

I have added de/serializers, as in ObjectMapper changes Date to 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;
            }
        }
    }
}

Of course, the call for the mapper looks a bit different:

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

Now the Date objects become String object in the map. With the dates properly represented in them. But I need them to remain to be of the Date type. What is interesting, if I put breakpoints in the deserialiser, it is never reached. So, the deserializer is never reached, I think, it is because the mapper after serializing makes Date a String or a Long, depending on SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, and never recognizes the Date's in the time of deserialization.

How can I let Date properties remain Date ones after mapping? I need them to be recognized.

BTW, the BigDecimal properties are turned into Double ones. It seems to be the similar problem, but these two types are not of much difference to my further work.

Gangnus
  • 24,044
  • 16
  • 90
  • 149

2 Answers2

3

Because you defined map's type of values as Object, Jackson doesn't select your custom deserializer of type Date and uses its default deserializer where it converts all types to basic ones (like long, String, LinkedHashMap etc.).

If you only had Date fields in your object, you could just change the 2nd argument of convertValue method:

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

But obviously it's not your case, so the most straightforward way to do it for an object with different types of fields is to change your deserializer type to Object and parse all data inside it manually:

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

Also, don't forget to replace the 1st argument of .addDeserializer with Object.class

For more exotic ways to do it, please check this article: http://robertmarkbramprogrammer.blogspot.com/2018/05/de-serialise-json-string-to-map-with.html

amseager
  • 5,795
  • 4
  • 24
  • 47
  • That looks promising and consistent. I am upvoting, and sorry, I will check how it works on Monday. Thank you. – Gangnus Feb 21 '20 at 17:04
  • Thank you very much. The ObjectMapper really loses types of objects. It is possible to workaround that by serializers that will add class info to their serialization. But I think, the best way is simply never to use that disgustfully dangerous function. Here (https://stackoverflow.com/questions/6796187/java-introspection-object-to-map) is possible to found much better solutions. – Gangnus Feb 24 '20 at 13:26
-1

You should use a POJO to represent your data, instead of a Map. Since a Map is ambiguous key-value pairs, there's no type information for each field and Jackson will read values without making any inferences about their type information. By modeling your data in a defined structure, Jackson will read values into the defined types.

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

With this approach, you may use Jackson's serialization features to convert dates using strings or integers. A custom (de)serializer isn't necessary.

sho
  • 759
  • 5
  • 16
  • Your proposition, sorry, is of no value for me. I am NOT asking for workaround. I want to have a map that will have an entry with value of type Date. And I am interested how ObjectMapper can do it. Your answer is that it cannot. Then there should be somewhere the list of types that CAN be worked with. For Double is NOT a String and it CAN be worked with. Please, could you provide some references that support your thought? – Gangnus Feb 21 '20 at 16:47
  • "By modeling your data in a defined structure, Jackson will read values into the defined types." - it is not so. My objects already have properties instead of simple fields. "a Map is ambiguous key-value pairs" - it needn't be so - the object can keep its type info. It is Jackson ObjectMapper that loses it. "You should use a POJO to represent your data, instead of a Map" - How can you tell that, not knowing why I need that map? Really, you would rather remove that answer. – Gangnus Feb 24 '20 at 13:32