Heruan Heruan - 2 months ago 12
Java Question

How to deserialize a Map<?, ?> with Jackson?

I'm trying to serialize/deserialize a

Map<?, ?>
with arbitrary object as keys with Jackson version 2.8. The JSON counterpart should be an array of couples, i.e. given

public class Foo {
public String foo;
public Foo(String foo) {
this.foo = foo;
}
}

public class Bar {
public String bar;
public Bar(String bar) {
this.bar = bar;
}
}


then

Map<Foo, Bar> map;
map.put(new Foo("foo1"), new Bar("bar1"));
map.put(new Foo("foo2"), new Bar("bar2"));


should be represented by this JSON

[
[ { "foo": "foo1" }, { "bar": "bar1" } ],
[ { "foo": "foo2" }, { "bar": "bar2" } ]
]


So I did the serializer part as

public class MapToArraySerializer extends JsonSerializer<Map<?, ?>> {

@Override
public void serialize(Map<?, ?> value, JsonGenerator gen, SerializerProvider serializers)
throws IOException, JsonProcessingException {
gen.writeStartArray();
for (Map.Entry<?, ?> entry : value.entrySet()) {
gen.writeStartArray();
gen.writeObject(entry.getKey());
gen.writeObject(entry.getValue());
gen.writeEndArray();
}
gen.writeEndArray();
}

}


but I have no idea how to write a
JsonDeserializer
to do the inverse job. Any suggestions?

Note: I need the
[ [ "key1", "value1" ], [ "key2", "value2" ] ]
notation to be able to consume that JSON in JavaScript a
new Map( ... )
and
JSON.stringify(map)
would produce that notation too (see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map).

To clarify, such a map would be a field of other classes, e.g.

public class Baz {

@JsonSerialize(using = MapToArraySerializer.class)
@JsonDeserialize(using = ArrayToMapDeserializer.class, keyAs = Foo.class, contentAs = Bar.class)
Map<Foo, Bar> map;

}


and
ArrayToMapDeserializer extends JsonDeserializer<Map<?, ?>>
is where I'm asking for help.

Answer

I came up with this solution:

public class ArrayToMapDeserializer extends JsonDeserializer<SortedMap<Object, Object>>
    implements ContextualDeserializer {

    private Class<?> keyAs;

    private Class<?> contentAs;

    @Override
    public Map<Object, Object> deserialize(JsonParser p, DeserializationContext ctxt)
        throws IOException, JsonProcessingException {
        return this.deserialize(p, ctxt, new HashMap<>());
    }

    @Override
    public Map<Object, Object> deserialize(JsonParser p, DeserializationContext ctxt,
        Map<Object, Object> intoValue) throws IOException, JsonProcessingException {
        JsonNode node = p.readValueAsTree();
        ObjectCodec codec = p.getCodec();
        if (node.isArray()) {
            node.forEach(entry -> {
                try {
                    JsonNode keyNode = entry.get(0);
                    JsonNode valueNode = entry.get(1);
                    intoValue.put(keyNode.traverse(codec).readValueAs(this.keyAs),
                        valueNode.traverse(codec).readValueAs(this.contentAs));
                } catch (NullPointerException | IOException e) {
                    // skip entry
                }
            });
        }
        return intoValue;
    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
        throws JsonMappingException {
        JsonDeserialize jsonDeserialize = property.getAnnotation(JsonDeserialize.class);
        this.keyAs = jsonDeserialize.keyAs();
        this.contentAs = jsonDeserialize.contentAs();
        return this;
    }

}

which can be used like this:

public class Baz {

    @JsonSerialize(using = MapToArraySerializer.class)
    @JsonDeserialize(using = ArrayToMapDeserializer.class,
        keyAs = Foo.class, contentAs = Bar.class)
    Map<Foo, Bar> map;

}
Comments