Search code examples
javaserializationdictionaryjacksonjackson-modules

Jackson Modules for Map Serialization


I have a Class that contains a Map (with non String key) and some other fields.

public class MyClass() {
    private Map<KeyObject, OtherObject> map;
    private String someField;

    public MyClass(Map<KeyObject, OtherObject> map, String someField) {
        this.map = map;
        this.someField = someField;
    }

    // Getters & Setters
}

I would like to serialize and deserialize this class using Jackson. I saw a different ways of doing that and decided to try using jackson modules.

I followed this post and extended JsonDeserializer and JsonSerializer. The problem is that those classes should be typed, so it should look like

public class keyDeserializer extends JsonDeserializer<Map<KeyObject, OtherObject>> {
...
}

The same for the KeySerializer.

Then adding to the module:

module.addSerializer(new keySerializer());
module.addDeserializer(Map.class, new keyDeserializer());

But this is wrong apparently since I'm getting an exception:

keySerializer does not define valid handledType() -- must either register with method that takes type argument  or make serializer extend 'org.codehaus.jackson.map.ser.std.SerializerBase'

I could have my serializer and deserializer to be typed to MyClass, but then I had to manually parse all of it, which is not reasonable.

UPDATE:

I managed to bypass the module creation in the code by using annotations

@JsonDeserialize(using = keyDeserializer.class)
@JsonSerialize(using = keySerializer.class)
private Map<KeyObject, OtherObject> map;

But then I have to serialize/deserialize the whole map structure on my own from the toString() output. So tried a different annotation:

@JsonDeserialize(keyUsing = MyKeyDeserializer.class)
private Map<KeyObject, OtherObject> map;

Where MyKeyDeserializer extends org.codehaus.jackson.map.KeyDeserializer and overriding the method

public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {...}

Then manually deserializing my key but again from the toString() output of my key class.

This is not optimal (this dependency on the toString() method). Is there a better way?


Solution

  • Ended up using this serializer:

    public class MapKeySerializer extends SerializerBase<Object> {
        private static final SerializerBase<Object> DEFAULT = new StdKeySerializer();
        private static final ObjectMapper mapper = new ObjectMapper();
    
        protected MapKeySerializer() {
        super(Object.class);
        }
    
        @Override
        public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
        return DEFAULT.getSchema(provider, typeHint);
        }
    
        @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
        if (null == value) {
            throw new JsonGenerationException("Could not serialize object to json, input object to serialize is null");
        }
        StringWriter writer = new StringWriter();
        mapper.writeValue(writer, value);
        jgen.writeFieldName(writer.toString());
        }
    }
    

    And this Deserializer:

    public class MapKeyDeserializer extends KeyDeserializer {
    
        private static final ObjectMapper mapper = new ObjectMapper();
    
        @Override
        public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        return mapper.readValue(key, MyObject.class);
        }
    }
    

    Annotated my Map:

    @JsonDeserialize(keyUsing = MapKeyDeserializer.class)
    @JsonSerialize(keyUsing = MapKeySerializer.class)
    private Map<KeyObject, OtherObject> map;
    

    This is the solution that worked for me, hope this helps other.