question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Jackson: Deserialize map with non String keys

See original GitHub issue

I’m trying to deserialize a map that contains keys that are not Strings. They do not seem to be deserialized properly.

For instance the library I am interfacing with, treats all strings as the binary type. (The encoding of the bytes not guaranteed to be utf-8 which is why they are not using the string type.)

So assuming I have a map of {a:1, b:2, c:3} where a, b and c are binary payloads. The following code tries to deserialize that into a Map<byte[], Integer>

ByteArrayOutputStream out = new ByteArrayOutputStream();
MessagePacker messagePacker = MessagePack.newDefaultPacker(out).packMapHeader(3);
for (int i = 0; i < 3; i++) {
    messagePacker
            .packBinaryHeader(1).writePayload(new byte[]{(byte) (i + 'a')})
            .packInt(i);
}
messagePacker.close();

ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory());
Map<byte[], Integer> map = objectMapper.readValue(
        out.toByteArray(), new TypeReference<Map<byte[], Integer>>() {});

This results in an exception

com.fasterxml.jackson.databind.JsonMappingException: Can not find a (Map) Key deserializer for type [array type, component type: [simple type, class byte]]
    at com.fasterxml.jackson.databind.deser.DeserializerCache._handleUnknownKeyDeserializer(DeserializerCache.java:608)
    at com.fasterxml.jackson.databind.deser.DeserializerCache.findKeyDeserializer(DeserializerCache.java:169)
    at com.fasterxml.jackson.databind.DeserializationContext.findKeyDeserializer(DeserializationContext.java:462)
    at com.fasterxml.jackson.databind.deser.std.MapDeserializer.createContextual(MapDeserializer.java:231)
    at com.fasterxml.jackson.databind.DeserializationContext.handleSecondaryContextualization(DeserializationContext.java:653)
    at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:444)
    at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:3666)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3558)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2688)

A quick search of this on google looks like I would need to add a KeyDeserializer. http://stackoverflow.com/questions/11246748/deserializing-non-string-map-keys-with-jackson. So I created a SimpleModule and registered it with the ObjectMapper.

ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory());
SimpleModule module = new SimpleModule();
module.addKeyDeserializer(byte[].class, new KeyDeserializer() {
            @Override
            public Object deserializeKey(String key, DeserializationContext ctxt) throws
                    IOException,
                    JsonProcessingException {
                throw new IOException("KeyDeserializer Called");
            }
        });
objectMapper.registerModule(module);

Map<byte[], Integer> map = objectMapper.readValue(
        out.toByteArray(), new TypeReference<Map<byte[], Integer>>() {});

However it doesn’t appear the KeyDeserializer ever called called and the returned map is empty.


This is also a problem with Integer keys (and I assume all other non string keys)

ByteArrayOutputStream out = new ByteArrayOutputStream();
MessagePacker messagePacker = MessagePack.newDefaultPacker(out).packMapHeader(3);
for (int i = 0; i < 3; i++) {
    messagePacker
            .packInt(i)
            .packInt(i);
}
messagePacker.close();

ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory());
Map<Integer, Integer> map = objectMapper.readValue(
        out.toByteArray(), new TypeReference<Map<Integer, Integer>>() {});

Exception is thrown without the KeyDeserializer. Adding the KeyDeserializer the map is empty.

(Side comment (and possibly another issue): Serializing a Map<Integer, Integer> with jackson actually outputs a map of Map<String, Integer> on the msgpack side which is undesirable.)

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
komamitsucommented, Aug 9, 2015

@fdinoff In https://github.com/msgpack/msgpack-java/issues/271, jackson-dataformat-msgpack supports Boolean, Integer and Float type key while byte array key seemed to work before the change.

BTW, you tried to use Map<byte[], Integer> map but I don’t think it’s easy to use byte[] as a key of Map since byte[]#equals doesn’t match other byte arrays which have the same values.

0reactions
fdinoffcommented, Aug 23, 2015

@komamitsu Sorry, didn’t realize that byte[] was a valid key now.

The problem now is what happens if the bytes weren’t valid utf8? is the conversion to String and back to a byte array lossless? The doc for String#getBytes() says its unspecified, so the conversion isn’t lossless. Losing valid data would be undesirable.

Also I don’t think you can deserialize into an actual object. (Is there?)

Example: You have the following class

class SomeObject {
    public Integer hello;
}

I would expect something like the following to work (the byte array is utf16 encoded.

ByteArrayOutputStream out = new ByteArrayOutputStream();
MessagePacker messagePacker = MessagePack.newDefaultPacker(out).packMapHeader(1);
byte[] bytes = "Hello".getBytes(StandardCharsets.UTF_16); 
messagePacker.packBinaryHeader(bytes.length).writePayload(bytes);
messagePacker.packInt(42);
messagePacker.close();

SomeObject o = objectMapper.readValue(out.toByteArray(), SomeObject.class);
assertEquals(42, o.hello);
Read more comments on GitHub >

github_iconTop Results From Across the Web

Deserializing non-string map keys with Jackson - Stack Overflow
After a day of searching, I came across a simpler way of doing it based on this question. The solution was to add...
Read more >
Map Serialization and Deserialization with Jackson - Baeldung
A quick and practical guide to serializing and deserializing Java Maps using Jackson.
Read more >
Definitive Guide to Jackson ObjectMapper - Serialize and ...
Convert JSON String to Java Map. The Map class is used to store key-value pairs in Java. JSON objects are key-value pairs, so...
Read more >
KeyDeserializer (jackson-databind 2.9.0 API) - FasterXML
Abstract class that defines API used for deserializing JSON content field names into Java Map keys. These deserializers are only used if the...
Read more >
Serializing map as list of pairs instead of having a key ...
Serializing map as list of pairs instead of having a key deserializer ... conversion from Map to List<Pair>, Jackson serialize that using default...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found