4

I have a nested Map<StructureNode, Map<String, String>> for which I need a custom key serializer & deserializer (StructureNode contains references to other objects which are needed to function as key for this map). I used the following method for this:

Jackson Modules for Map Serialization

Giving the following result. Custom Serializer:

public class StructureNodeKeySerializer extends JsonSerializer<StructureNode> {

    private static final ObjectMapper mapper = new ObjectMapper();

    @Override
    public void serialize(StructureNode value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        StringWriter writer = new StringWriter();
        mapper.writeValue(writer, value.copyUpwards());
        gen.writeFieldName(writer.toString());
    }
}

Custom deserializer:

public class StructureNodeKeyDeserializer extends KeyDeserializer  {

    private static final ObjectMapper mapper = new ObjectMapper();

    @Override
    public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
        return mapper.readValue(key, StructureNode.class);
    }
}

Usage:

@JsonDeserialize(keyUsing = StructureNodeKeyDeserializer.class) @JsonSerialize(keyUsing = StructureNodeKeySerializer.class)
private Map<StructureNode, String> structureIds;
@JsonDeserialize(keyUsing = StructureNodeKeyDeserializer.class) @JsonSerialize(keyUsing = StructureNodeKeySerializer.class)
private Map<StructureNode, Map<String, String>> metadata;

This correctly serializes a Map<StructureNode, String>, but applied to a nested Map<StructureNode, Map<String, String>>, it gives the following error:

Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: java.lang.String cannot be cast to structure.StructureNode

Jackson seems to be using the same custom serialization method for the "sub-map". Is there a good way to solve this problem, without replacing the "sub-map" with another custom (non-Map) object?

tb189
  • 1,942
  • 3
  • 22
  • 37
  • You shouldn't be using a private `ObjectMapper` in your serialiser and deserialiser. – teppic Oct 19 '17 at 18:39
  • @teppic Why do you say that? I was following the example in the other post I mentioned, but happy to improve it. But either way, that doesn't solve the exception I'm getting. – tb189 Oct 19 '17 at 19:04
  • Because it's separate from the one that the client code is using (and may have instrumented). – teppic Oct 19 '17 at 19:12

2 Answers2

3

You can fix this with

public static class Bean{
    @JsonSerialize(using = MapStructureNodeKeySerializer.class)
    public Map<StructureNode, Map<String, String>> metadata;
}

And implement your serializer a little bit differently:

public static class MapStructureNodeKeySerializer 
        extends JsonSerializer<Map<StructureNode, Object>> {

    private static final ObjectMapper mapper = new ObjectMapper();

    @Override
    public void serialize(Map<StructureNode, Object> value, JsonGenerator gen, 
                          SerializerProvider serializers) throws IOException {
        gen.writeStartObject();

        for(Map.Entry<StructureNode, Object> val: value.entrySet()){
            // your custom serialization code here
            StringWriter writer = new StringWriter();
            mapper.writeValue(writer, val.getKey().copyUpwards());

            gen.writeObjectField(writer.toString(), val.getValue());
        }

        gen.writeEndObject();
    }
}

Or if you want to keep keyUsing = StructureNodeKeySerializer.class

public static class Bean{
    @JsonSerialize(keyUsing = StructureNodeKeySerializer.class)
    public Map<StructureNode, Map<String, String>> metadata;
}

You can implement it like:

public static class StructureNodeKeySerializer extends JsonSerializer {

    private static final ObjectMapper mapper = new ObjectMapper();

    @Override
    public void serialize(Object value, JsonGenerator gen,
                          SerializerProvider serializers) throws IOException {

        if (value instanceof StructureNode){ // <= type of 1-st level Map key
            // your custom serialization code here
            StringWriter writer = new StringWriter();
            mapper.writeValue(writer, ((StructureNode)value).copyUpwards());
            gen.writeFieldName(writer.toString());
        }else if(value instanceof String){   // <= type of 2-nd level Map key
            gen.writeFieldName((String) value);
        }
    }
}
varren
  • 14,551
  • 2
  • 41
  • 72
0

If you want to serialize it more generically as keySerializer, you can rewrite the else clause as follows

if (value instanceof StructureNode) {
  // ...
} else {
  serializers
    .findKeySerializer(value.class, null)
    .serialize(value, gen, serializers);
}
wrongwrong
  • 191
  • 5