Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson: Deserialize to a Map<String, Object> with correct type for each value

I have a class that looks like the following

public class MyClass {    private String val1;    private String val2;    private Map<String,Object> context;    // Appropriate accessors removed for brevity.    ... } 

I'm looking to be able to make the round trip with Jackson from object to JSON and back. I can serialize the object above fine and receive the following output:

{     "val1": "foo",     "val2": "bar",     "context": {         "key1": "enumValue1",         "key2": "stringValue1",         "key3": 3.0     } } 

The issue I'm running into is that since the values in the serialized map do not have any type information, they are not deserialized correctly. For example, in the sample above, enumValue1 should be deserialized as an enum value but is instead deserialized as a String. I've seen examples for basing what type on a variety of things, but in my scenario, I won't know what the types are (they will be user generated objects that I won't know in advance) so I need to be able to serialize the type information with the key value pair. How can I accomplish this with Jackson?

For the record, I'm using Jackson version 2.4.2. The code I'm using to test the round trip is as follows:

@Test @SuppressWarnings("unchecked") public void testJsonSerialization() throws Exception {     // Get test object to serialize     T serializationValue = getSerializationValue();     // Serialize test object     String json = mapper.writeValueAsString(serializationValue);     // Test that object was serialized as expected     assertJson(json);     // Deserialize to complete round trip     T roundTrip = (T) mapper.readValue(json, serializationValue.getClass());     // Validate that the deserialized object matches the original one     assertObject(roundTrip); } 

Since this is a Spring based project, the mapper is being created as follows:

@Configuration public static class SerializationConfiguration {      @Bean     public ObjectMapper mapper() {         Map<Class<?>, Class<?>> mixins = new HashMap<Class<?>, Class<?>>();         // Add unrelated MixIns         ..           return new Jackson2ObjectMapperBuilder()                 .featuresToDisable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS)                 .dateFormat(new ISO8601DateFormatWithMilliSeconds())                 .mixIns(mixins)                 .build();     } } 
like image 835
Michael Minella Avatar asked Jan 09 '15 23:01

Michael Minella


People also ask

How do you serialize a map object in Java?

We are using writeObject() method of ObjectOutputStream to serialize HashMap in Java. In the following program, we save the hashmap content in a serialized newHashMap file. Once you run the following code, a newHashMap file will be created. This file is used for deserialization in the next upcoming program.

What is type reference in Jackson?

Class TypeReference<T>This generic abstract class is used for obtaining full generics type information by sub-classing; it must be converted to ResolvedType implementation (implemented by JavaType from "databind" bundle) to be used.


1 Answers

I think the simplest way of achieve what you want is using:

ObjectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 

This will add type information in the serialized json.

Here you are a running example, that you will need to adapt to Spring:

public class Main {      public enum MyEnum {         enumValue1     }      public static void main(String[] args) throws IOException {         ObjectMapper mapper = new ObjectMapper();          MyClass obj = new MyClass();         obj.setContext(new HashMap<String, Object>());          obj.setVal1("foo");         obj.setVal2("var");         obj.getContext().put("key1", "stringValue1");         obj.getContext().put("key2", MyEnum.enumValue1);         obj.getContext().put("key3", 3.0);          mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);         String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);          System.out.println(json);          MyClass readValue = mapper.readValue(json, MyClass.class);         //Check the enum value was correctly deserialized         Assert.assertEquals(readValue.getContext().get("key2"), MyEnum.enumValue1);     }  } 

The object will be serialized into something similar to:

[ "so_27871226.MyClass", {   "val1" : "foo",   "val2" : "var",   "context" : [ "java.util.HashMap", {     "key3" : 3.0,     "key2" : [ "so_27871226.Main$MyEnum", "enumValue1" ],     "key1" : "stringValue1"   } ] } ] 

And will be deserialized back correctly, and the assertion will pass.

Bytheway there are more ways of doing this, please look at https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization for more info.

I hope it will help.

like image 182
fonkap Avatar answered Sep 19 '22 07:09

fonkap