skip to Main Content

Im trying to override the default KeyDeserializer but it seems to work only on top level key:

The input JSON

{
    "upper_key":
    {
        "lower_key": "lower_value",
        "lower_key_2": "lower_value_2"
    },
    "upper_key_2": "upper_value_2"
}

The mapper I use

    ObjectMapper mapper = new ObjectMapper();
    SimpleModule module = new SimpleModule();
    module.addKeyDeserializer(Object.class, new MyKeyParser());
    mapper.registerModule(module);
    Map<String, Object> map = mapper.readValue(jsonString, Map.class);

and the KeyDeserializer

public class MyKeyParser extends KeyDeserializer {

  @Override
  public String deserializeKey(String key, DeserializationContext deserializationContext) {
    System.out.println(key);
    return key;
  }

}

The print output for the given json is:

upper_key
upper_key_2

What I expected it to be (order doesn’t matter)

upper_key
lower_key
lower_key_2
upper_key_2

What I’m missing?

EDIT:
I see some answers give alternative ways to get the keys but this is not the question.
I want to understand how to use the KeyDeserializer.
My real intent is to alter the keys – for example add suffix. so I don’t want to iterate over the map twice and create 2 maps.

I know I can write a Map.class Deserializer and do everything there. But the question is why the KeyDeserializer work only on top level keys and not how to manipulate maps.

Thank you

4

Answers


  1. There is no need for overriding a Deserializer. For dynamic JSONs you could simply use a JsonNode and then parse the structure however you want afterwards:

    public static void main(String[] args) throws Exception {
        JsonNode jsonNode = new ObjectMapper().readValue("""
                {
                    "upper_key":
                    {
                        "lower_key": "lower_value",
                        "lower_key_2": "lower_value_2"
                    },
                    "upper_key_2": "upper_value_2"
                }""", JsonNode.class);
    
        extractKeys(jsonNode);
    }
    
    public static void extractKeys(JsonNode jsonNode) {
    
        jsonNode.fieldNames().forEachRemaining(name -> {
            System.out.println(name);
            JsonNode subnode = jsonNode.get(name);
    
            if (subnode.isObject()) {
                extractKeys(subnode);
            }
        });
    }
    

    prints:

    upper_key
    lower_key
    lower_key_2
    upper_key_2
    
    Login or Signup to reply.
  2. KeyDeserializer call deserialize Key just for upper key. but you can get all keys by creating your own implementation for example

    private static void getAllKeysUsingJsonNodeFieldNames(JsonNode value, List<String> keys) {
            if (value.isObject()) {
                Iterator<Entry<String, JsonNode>> fields = value.fields();
                fields.forEachRemaining(field -> {
                    keys.add(field.getKey());
                    getAllKeysUsingJsonNodeFieldNames((JsonNode) field.getValue(), keys);
                });
            } else if (value.isArray()) {
                ArrayNode arrayField = (ArrayNode) value;
                arrayField.forEach(node -> {
                    getAllKeysUsingJsonNodeFieldNames(node, keys);
            });
        }
    }
    

    and call it from your main class

    public static void main(String[] args) throws JsonMappingException, JsonProcessingException {
            ObjectMapper mapper = new ObjectMapper();
            List<String> keys = new ArrayList<>();
            JsonNode jsonNode = mapper.readValue(jsonString, JsonNode.class);
            getAllKeysUsingJsonNodeFieldNames(jsonNode, keys);
            System.out.println(keys);
    }
    
    Login or Signup to reply.
  3. Let’s look at KeyDeserializer javadoc:

    KeyDeserializer: class that defines API used for deserializing JSON content field names into Java Map keys.

    Note word keys here. KeyDeserializer is used only for map keys deserialization. In your case your target type is Map<String, Object>. Any other types aren’t affected by KeyDeserializer. That is the reason why your code behaves like that. Jackson basically does next steps:

    1. It sees target type as Map<String, Object>. It means that for deserializaiton MapDeserializer class should be used.
    2. MapDeserializer uses for keys deserialization class that you provided.
    3. For values deserialization UntypedObjectDeserializer is used, because value type is object.

    With your initial JSON it is impossible to apply key deserializer so lower keys will be affected by key deserializer: you need to have Map type instead of object. If you will use next JSON (I removed second upper key from original JSON because I want object to strictly match Map<String, Map<String, String>>):

    {
        "upper_key":
        {
            "lower_key": "lower_value",
            "lower_key_2": "lower_value_2"
        }
    }
    

    and next code (Note that key deserializer registered for Object type, and map typed as "Object to Map", i.e. "Object to Map<Object, Object>"):

    ObjectMapper mapper = new ObjectMapper();
    SimpleModule module = new SimpleModule();
    module.addKeyDeserializer(Object.class, new MyKeyParser());
    
    mapper.registerModule(module);
    
    MapType mapType = mapper.getTypeFactory().constructMapType(Map.class, Object.class, Map.class);
    
    Map<String, Map<String, String>> map = mapper.readValue(jsonString, mapType);
    

    output will be as you want:

    upper_key
    lower_key
    lower_key_2
    
    Login or Signup to reply.
  4. The fact that Jackson doesn’t use the KeyDeserializer for nested objects is explained by Le0pold’s answer.
    However, there is a trick to make it work as expected (tested with Jackson 2.12.7): call
    addAbstractTypeMapping() like this:

    import java.util.*;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.*;
    import com.fasterxml.jackson.databind.module.SimpleModule;
    
    public class Test
    {
        public static void main(String[] args) throws JsonProcessingException
        {
            ObjectMapper mapper = new ObjectMapper();
            SimpleModule module = new SimpleModule();
            module.addKeyDeserializer(Object.class, new MyKeyParser());
            module.addAbstractTypeMapping(Map.class, LinkedHashMap.class); // this line does the trick
            mapper.registerModule(module);
    
            String jsonString = "{"upper_key": {"lower_key": "lower_value", "lower_key_2": "lower_value_2"}, "upper_key_2": "upper_value_2"}";
            Map map = mapper.readValue(jsonString, Map.class);
        }
    
        public static class MyKeyParser extends KeyDeserializer
        {
            @Override
            public Object deserializeKey(String key, DeserializationContext deserializationContext)
            {
                System.out.println(key);
                return key;
            }
        }
    }
    

    Output:

    upper_key
    lower_key
    lower_key_2
    upper_key_2
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search