Jackson: Keep references to keys in map values when deserializing

I have the following JSON with a map from user IDs to user details:

{
    "users": {
        "john": { "firstName": "John", "lastName": "Doe" },
        "mark": { "firstName": "Mark", "lastName": "Smith" }
    }
}

and I'm using the following code to deserialize the JSON into a Java objects:

class User {
    public String userID;

    public String firstName;
    public String lastName;
}

public class Users {
    public Map<String, User> users;

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        Reader source = Files.newBufferedReader(Paths.get("test.json"));
        Users all = mapper.readValue(source, Users.class);
        // ...
    }
}

After the deserialization, I want the field User.userID to be set to the corresponding key in the users map.

For example all.users.get("john").userID should be "john".

How can I do that?

Create a custom deserializer for User object and use this for the Map. Here's a full example:

@Test
public void test() throws JsonParseException, JsonMappingException, IOException {
    ObjectMapper mapper = new ObjectMapper();

    Data data = mapper.readValue("{\"users\": {\"John\": {\"id\": 20}, \"Pete\": {\"id\": 30}}}", Data.class);

    assertEquals(20, data.users.get("John").id);
    assertEquals(30, data.users.get("Pete").id);
    assertEquals("John", data.users.get("John").name);
    assertEquals("Pete", data.users.get("Pete").name);
}

public static class Data {
    @JsonDeserialize(contentUsing = Deser.class)
    public Map<String, User> users;
}

public static class User {
    public String name;
    public int id;
}

public static class Deser extends JsonDeserializer<User> {

    @Override
    public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        String name = ctxt.getParser().getCurrentName();

        User user = p.readValueAs(User.class);

        user.name = name;  // This copies the key name to the user object

        return user;
    }
}

Map Serialization and Deserialization with Jackson, Create a custom deserializer for User object and use this for the Map. Here's a full example: @Test public void test() throws� Serialization converts a Java object into a stream of bytes, which can be persisted or shared as needed. Java Maps are collections which map a key Object to a value Object and are often the least intuitive objects to serialize. 3.1. Map<String, String> Serialization

The simplest solution for the problem is to implement a custom deserializer for the class in which you need the map key (see john16384's answer). This is however cumbersome if you have multiple maps with different value types in your JSON because you'd need one deserializer per type.

In this case, there is a better solution: I would create a custom @JsonMapKey annotation to mark the target properties for the map keys, and then register a generic custom deserializer that processes all occurrences of the annotation. These are the parts you need for this:

Custom @JsonMapKey annotation:

/**
 * Annotation used to indicate that the annotated property shall be deserialized to the map key of
 * the current object. Requires that the object is a deserialized map value.
 * 
 * Note: This annotation is not a standard Jackson annotation. It will only work if this is
 * explicitly enabled in the {@link ObjectMapper}.
 */
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonMapKey {

}

Custom deserializer that processes the @JsonMapKey annotations:

public class JsonMapKeyDeserializer extends DelegatingDeserializer {

    private static final long serialVersionUID = 1L;
    private BeanDescription beanDescription;

    public JsonMapKeyDeserializer(JsonDeserializer<?> delegate, BeanDescription beanDescription) {
        super(delegate);
        this.beanDescription = beanDescription;
    }

    @Override
    protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
        return new JsonMapKeyDeserializer(newDelegatee, beanDescription);
    }

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String mapKey = p.getCurrentName();
        Object deserializedObject = super.deserialize(p, ctxt);

        // set map key on all fields annotated with @JsonMapKey
        for (BeanPropertyDefinition beanProperty : beanDescription.findProperties()) {
            AnnotatedField field = beanProperty.getField();
            if (field != null && field.getAnnotation(JsonMapKey.class) != null) {
                field.setValue(deserializedObject, mapKey);
            }
        }
        return deserializedObject;
    }
}

Registration of the custom deserializer in the ObjectMapper:

private static void registerJsonMapKeyAnnotation(ObjectMapper objectMapper) {
    SimpleModule module = new SimpleModule();
    module.setDeserializerModifier(new BeanDeserializerModifier() {
        @Override
        public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, 
                BeanDescription beanDescription, JsonDeserializer<?> originalDeserializer) {
            return new JsonMapKeyDeserializer(originalDeserializer, beanDescription);
        }
    });
    objectMapper.registerModule(module);
}

Then you only need to annotate the field to be used for the map key...

class User {
    @JsonMapKey
    public String userID;

    public String firstName;
    public String lastName;
}

... and deserialize your JSON with the prepared ObjectMapper:

Users all = registerJsonMapKeyAnnotation(new ObjectMapper()).readValue(source, Users.class);

Jackson, Java Maps are collections which map a key Object to a value Object and are often the least intuitive objects to serialize. 3.1. Map<String, String>� In these cases, you can instruct Jackson to deserialize the object to a Map. It will contain field names as keys and their deserialized contents as values. Note that the default deserializers apply for basic field types in the Map. Jackson deserializes the date as a collection, same as the array of hobbies.

First Create the ObjectMapper class object than configure it.

Try following one.

Sample Code

Map<K, V> map;
ObjectMapper mapper = new ObjectMapper();
mapper.configure(Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
map = mapper.readValue(jsonStr, new TypeReference<Map<K, V>>() {});

than you can get the value using Map.

Deserialization support for ES6 map (array of key/value pairs) � Issue , How to serialize Maps with a null key or null values using Jackson. Now any null value in Map object serialized through this mapper is going� This article shows how to leverage Jackson 2 to read non-standard JSON input – and how to map that input to any java entity graph with full control over the mapping. The implementation of all these examples and code snippets can be found in over on GitHub – it's a Maven-based project, so it should be easy to import and run as it is.

Ability to ignore keys when deserializing maps � Issue #1479 , Jackson has a MapDeserializer which deserializes JSON-Objects into key it was common to use such Objects as key-value maps in a JavaScript program. . org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) which where we need to keep maintaining the compatibility with all jackson� The @JsonAnyGetter annotation allows the flexibility of using a Map field as standard properties. Here's a quick example – the ExtendableBean entity has the name property and a set of extendable attributes in the form of key/value pairs:

Ability to ignore keys when deserializing maps #1479 Jackson chokes on this because 1. the key is not a valid enum value and 2. the probably avoid serializing data structures with circular references altogether, but it did� Now, the return value of getMeters() method represents the Enum objects. Thus, when deserializing the sample JSON: {"distance":"0.0254"} Jackson will look for the Enum object that has a getMeters() return value of 0.0254. In this case, the object is Distance.INCH: assertEquals(Distance.INCH, city.getDistance()); 4.3.

Find local businesses, view maps and get driving directions in Google Maps. When you have eliminated the JavaScript , whatever remains must be an empty page. Enable JavaScript to see Google Maps.

Comments
  • I also filed a feature request to have Jackson support @JsonMapKey or a similar annotation out of the box.
  • This does not answer the question