How to customly serialize or convert a Map property with custom key type in Jackson JSON (de-)serialization? How to customly serialize or convert a Map property with custom key type in Jackson JSON (de-)serialization? json json

How to customly serialize or convert a Map property with custom key type in Jackson JSON (de-)serialization?


The problem here is that when you use Map.Entry the key has to be a string, because it gets serialized like {"key": value}.


You have two options


Your first option if you can serialize your object as string you can use it as the json key.

This is posible in two cases, when the object contains a single field (like the one in your example). e.g.

new SingleFieldObject(2l) // can be serialized as "2"

Or when constains multiple fields that can be represented as string. e.g.

new MultipleFieldObject("John", 23) // can be serialized as "John 23 Years Old"

Now that the custom object can be represented as string you could use either a map or a list of entries.

To use a simple map just use the attribute 'keyUsing' in the annotations, and also you have to define the custom serializer and deserializer.

public class MyKeyDeserializer extends KeyDeserializer {    @Override    public Entity2 deserializeKey(String key,                                  DeserializationContext ctxt) throws IOException {        return new Entity2(Long.parseLong(key));    }}public class MyKeySerializer extends JsonSerializer<Entity2> {    @Override    public void serialize(Entity2 value,                          JsonGenerator gen,                          SerializerProvider serializers) throws IOException {        gen.writeFieldName(value.getId().toString());    }}

Then you annotate the field with your serializer and deserializer:

@JsonSerialize(keyUsing = MyKeySerializer.class) // no need of converter@JsonDeserialize(keyUsing = MyKeyDeserializer.class) // no need of converterprivate Map<Entity2, Integer> valueMap = new HashMap<>();

Using this object.

Entity1 entity1 = new Entity1(1l);Entity2 entity2_1 = new Entity2(2l);Entity2 entity2_2 = new Entity2(3l);entity1.getValueMap().put(entity2_1, 21);entity1.getValueMap().put(entity2_2, 22);

A JSON like this is generated

{    "id": 1,    "valueMap": {        "2": 21,        "3": 22    }}

To use a list you could use the converters in your example, but instead Entity2 you return a String for the key.

public class ValueMapListConverter     extends StdConverter<Map<Entity2, Integer>, List<Entry<String, Integer>>> {    @Override    public List<Entry<String, Integer>> convert(Map<Entity2, Integer> value) {        List<Entry<String, Integer>> result = new ArrayList<>();        for (Entry<Entity2, Integer> entry : value.entrySet()) {            result.add(new SimpleEntry<>(entry.getKey().getId().toString(),                        entry.getValue()));        }        return result;    }}public class ValueMapMapConverter     extends StdConverter<List<Entry<String, Integer>>, Map<Entity2, Integer>> {    @Override    public Map<Entity2, Integer> convert(List<Entry<String, Integer>> value) {        Map<Entity2, Integer> retValue = new HashMap<>();        for(Entry<String, Integer> entry : value) {            retValue.put(new Entity2(Long.parseLong(entry.getKey())), entry.getValue());        }        return retValue;    }}

A JSON like this is generated

{    "id": 1,    "valueMap": [        { "2": 21 },        { "3": 22 }    ]}

In both cases the value Integer could be a complex object.


Your second option is to use a custom object, again you have multiple options, one object that hold all the fields of the key and the field/fields of the value.

// ... serialization - deserialization of the objectpublic class CustomObject {    private Long id; // ... all key fields    private int value; // ... all value fields}

Then you use the converterspublic class ValueListMapConverter extends StdConverter<List<CustomObject>, Map<Entity2, Integer>> and public class ValueMapMapConverter extends StdConverter<Map<Entity2, Integer>, List<CustomObject>>

This generates a JSON like this

{    "id": 1,    "valueMap": [        { "id": 2, "value": 21 },        { "id": 3, "value": 22 }    ]}

You could use a map instead a list and use a key, and the rest of the fields of the key object, together with the value fields in a custom object.

// ... serialization - deserialization of the objectpublic class CustomObject {    // ... rest of the key fields    private int value; // ... all value fields}

The converterspublic class ValueListMapConverter extends StdConverter<Map<Long, CustomObject>, Map<Entity2, Integer>> and public class ValueMapMapConverter extends StdConverter<Map<Entity2, Integer>, Map<Long, CustomObject>>

This generates a JSON like this

{    "id": 1,    "valueMap": {        "2": { "value": 21 },        "3": { "value": 22 },    }}