30

I have a a map that looks like this:

public class VerbResult {
    @JsonProperty("similarVerbs")
    private Map<Verb, List<Verb>> similarVerbs;
}

My verb class looks like this:

public class Verb extends Word {
    @JsonCreator
    public Verb(@JsonProperty("start") int start, @JsonProperty("length") int length,
            @JsonProperty("type") String type, @JsonProperty("value") VerbInfo value) {
        super(length, length, type, value);
    }
    //...
}

I want to serialize and deserialize instances of my VerbResult class, but when I do I get this error: Can not find a (Map) Key deserializer for type [simple type, class my.package.Verb]

I read online that you need to tell Jackson how to deserialize map keys, but I didn't find any information explaining how to go about doing this. The verb class needs to be serialized and deserialzed outside of the map as well, so any solution should preserve this functionality.

Thank you for your help.

Max
  • 15,157
  • 17
  • 82
  • 127
  • 1
    have you read http://stackoverflow.com/questions/6371092/can-not-find-a-map-key-deserializer-for-type-simple-type-class-com-comcast-i ? His case looks quite similar to yours at first sight. – fvu Jun 28 '12 at 14:29
  • 1
    Yes I have read that, but I didn't actually find an answer to the question in there. How can I use a module to solve this problem? – Max Jun 28 '12 at 14:33
  • Is `Verb` a POJO? – Kalle Richter Mar 11 '18 at 12:57

5 Answers5

43

After a day of searching, I came across a simpler way of doing it based on this question. The solution was to add the @JsonDeserialize(keyUsing = YourCustomDeserializer.class) annotation to the map. Then implement your custom deserializer by extending KeyDeserializer and override the deserializeKey method. The method will be called with the string key and you can use the string to build the real object, or even fetch an existing one from the database.

So first in the map declaration:

@JsonDeserialize(keyUsing = MyCustomDeserializer.class)
private Map<Verb, List<Verb>> similarVerbs;

Then create the deserializer that will be called with the string key.

public class MyCustomDeserializer extends KeyDeserializer {
    @Override
    public MyMapKey deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        //Use the string key here to return a real map key object
        return mapKey;
    }
}

Works with Jersey and Jackson 2.x

Community
  • 1
  • 1
13

As mentioned above the trick is that you need a key deserializer (this caught me out as well). In my case a non-String map key was configured on my class but it wasn't in the JSON I was parsing so an extremely simple solution worked for me (simply returning null in the key deserializer).

public class ExampleClassKeyDeserializer extends KeyDeserializer
{
    @Override
    public Object deserializeKey( final String key,
                                  final DeserializationContext ctxt )
       throws IOException, JsonProcessingException
    {
        return null;
    }
}

public class ExampleJacksonModule extends SimpleModule
{
    public ExampleJacksonModule()
    {
        addKeyDeserializer(
            ExampleClass.class,
            new ExampleClassKeyDeserializer() );
    }
}

final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule( new ExampleJacksonModule() );
jdt
  • 131
  • 1
  • 2
6

Building on the answer given here that suggests to implement a Module with a deserializer. The JodaTime Module is an easy to understand full example of a module containing serializers and deserializers.

Please note that the Module feature was introduced in Jackson version 1.7 so you might need to upgrade.

So step by step:

  1. create a module containing a (de)serializer for your class based on the Joda example
  2. register that module with mapper.registerModule(module);

and you'll be all set

fvu
  • 32,488
  • 6
  • 61
  • 79
  • 4
    One more additional note: what you need to add are "key (de)serializers": regular (de)serializers can not be used as is, because of additional limitations Map keys have. But registration can be done from module for sure. – StaxMan Jun 29 '12 at 16:51
  • 5
    @Max Can you perhaps provide code for how you implemented the deserializer? I would like to see that, if possible =) – Ted Jan 27 '13 at 16:17
2

Assuming we have a Map property, like the following:

class MyDTO{
    @JsonSerialize(keyUsing = MyObjectKeySerializer.class)
    @JsonDeserialize(keyUsing = MyObjectKeyDeserilazer.class)
    private Map<MyObjectKey , List<?>> map;
}

We serilize the MyObjectKey as a json string, while call objectMapper.writeAsString; And deserilize from the json string,to MyObjectKey

    public class MyObjectKeySerializer extends StdSerializer<MyObjectKey> {

        public Serializer() {
            super(MyObjectKey.class);
        }

        @Override
        public void serialize(MyObjectKey value, JsonGenerator gen, SerializerProvider provider) throws IOException {
            gen.writeFieldName(JsonUtil.toJSONString(value));
        }
    }

    public class MyObjectKeyDeserializer extends KeyDeserializer {
        @Override
        public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
            return JsonUtil.toObject(key, MyObjectKey.class);
        }
    }
Evaldas Buinauskas
  • 13,739
  • 11
  • 55
  • 107
light
  • 113
  • 4
0

After scouring the web, I think I have a decent solution for how to handle POJO-style keys (although, as always, you are best served not using a full object as a map key).

Serializer (registered as a Jackson module, inside of Spring Boot):

@Bean
fun addKeySerializer(): Module =
    SimpleModule().addKeySerializer(YourClass::class.java, YourClassSerializer())

class YourClassSerializer() : JsonSerializer<YourClass>() {
    override fun serialize(value: DataElement, gen: JsonGenerator, serializers: SerializerProvider) {
        gen.writeFieldName(jacksonObjectMapper().writeValueAsString(value))
    }
}

(note that, in a standard Java environment, you will have to instantiate your own objectMapper instance here)

Deserializer:

@Bean
fun addKeyDeserializer(): Module =
    SimpleModule().addKeyDeserializer(YourClass::class.java, YourClassDeserializer())

class YourClassDeserializer() : KeyDeserializer() {
    override fun deserializeKey(key: String, ctxt: DeserializationContext): YourClass? {
        return ctxt.parser.readValueAs(YourClass::class.java)
    }
}
JasonB
  • 302
  • 1
  • 3
  • 13