CustomMapKeyDeserializationTest.java

package tools.jackson.databind.deser.jdk;

import java.util.Collections;
import java.util.Map;

import org.junit.jupiter.api.Test;

import tools.jackson.core.JsonGenerator;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.*;
import tools.jackson.databind.annotation.JsonDeserialize;
import tools.jackson.databind.annotation.JsonSerialize;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.module.SimpleModule;

import static org.junit.jupiter.api.Assertions.assertEquals;

import static tools.jackson.databind.testutil.DatabindTestUtil.a2q;
import static tools.jackson.databind.testutil.DatabindTestUtil.jsonMapperBuilder;
import static tools.jackson.databind.testutil.DatabindTestUtil.newJsonMapper;

public class CustomMapKeyDeserializationTest
{
    @JsonDeserialize(keyUsing = Key2454Deserializer.class)
    @JsonSerialize(keyUsing = Key2454Serializer.class)
    static class Key2454 {
        String id;

        public Key2454(String id, boolean bogus) {
            this.id = id;
        }
    }

    static class Key2454Deserializer extends KeyDeserializer {
        @Override
        public Object deserializeKey(String key, DeserializationContext ctxt) {
            return new Key2454(key, false);
        }
    }

    static class Key2454Serializer extends ValueSerializer<Key2454> {
        @Override
        public void serialize(Key2454 value, JsonGenerator gen,
                SerializationContext serializers) {
            gen.writeName("id="+value.id);
        }
    }

    // [databind#4444]
    @JsonDeserialize(keyUsing = Key4444ForClass.class)
    static class Key4444 {
        final String value;

        Key4444(String value) {
            this.value = value;
        }
    }

    static class Key4444ForClass extends KeyDeserializer {
        @Override
        public Object deserializeKey(String key, DeserializationContext ctxt) {
            return new Key4444(key + "-class");
        }
    }

    static class Key4444ForMapper extends KeyDeserializer {
        @Override
        public Object deserializeKey(String key, DeserializationContext ctxt) {
            return new Key4444(key + "-mapper");
        }
    }

    /*
    /**********************************************************************
    /* Test methods
    /**********************************************************************
     */

    private final ObjectMapper MAPPER = newJsonMapper();

    // For [databind#2454]
    
    @Test
    public void testCustomSerializer() throws Exception
    {
        assertEquals(a2q("{'id=a':'b'}"),
                MAPPER.writeValueAsString(Collections.singletonMap(new Key2454("a", true), "b")));
    }

    @Test
    public void testCustomDeserializer() throws Exception
    {
        Map<Key2454, String> result = MAPPER.readValue(a2q("{'a':'b'}"),
                new TypeReference<Map<Key2454, String>>() { });
        assertEquals(1, result.size());
        Key2454 key = result.keySet().iterator().next();
        assertEquals("a", key.id);
    }

    // [databind#4444]: annotation-based key deserializer should not be overwritten by mapper-level one
    @Test
    public void testAnnotationKeyDeserWithoutMapperOverride4444() throws Exception {
        TypeReference<Map<Key4444, String>> typeRef = new TypeReference<Map<Key4444, String>>() {};
        Map<Key4444, String> result = MAPPER.readValue("{\"foo\":null}", typeRef);

        assertEquals("foo-class", result.keySet().stream().findFirst().get().value);
    }

    // [databind#4444]
    @Test
    public void testAnnotationKeyDeserWithMapperOverride4444() throws Exception {
        SimpleModule sm = new SimpleModule();
        sm.addKeyDeserializer(Key4444.class, new Key4444ForMapper());

        ObjectMapper mapper = jsonMapperBuilder().addModule(sm).build();
        TypeReference<Map<Key4444, String>> typeRef = new TypeReference<Map<Key4444, String>>() {};
        Map<Key4444, String> result = mapper.readValue("{\"foo\":null}", typeRef);

        assertEquals("foo-class", result.keySet().stream().findFirst().get().value);
    }

    // [databind#4680]: Custom key deserializer registered for `Object.class` on nested JSON
    @SuppressWarnings("unchecked")
    @Test
    public void testCustomKeyDeserializerNested4680() throws Exception
    {
        String json =
                "{\n" +
                        "     \"name*\": \"Erik\",\n" +
                        "     \"address*\": {\n" +
                        "         \"city*\": {\n" +
                        "             \"id*\": 1,\n" +
                        "             \"name*\": \"Berlin\"\n" +
                        "         },\n" +
                        "         \"street*\": \"Elvirastr\"\n" +
                        "     }\n" +
                        " }";

        SimpleModule keySanitizationModule = new SimpleModule("key-sanitization");
        keySanitizationModule.addKeyDeserializer(String.class, new KeyDeserializer() {
            @Override
            public String deserializeKey(String key, DeserializationContext ctxt) {
                return key.replace("*", "_");
            }
        });
        keySanitizationModule.addKeyDeserializer(Object.class, new KeyDeserializer() {
            @Override
            public Object deserializeKey(String key, DeserializationContext ctxt) {
                return key.replace("*", "_");
            }
        });

        ObjectMapper mapper = JsonMapper.builder().addModule(keySanitizationModule).build();

        Map<String, Object> result = mapper.readValue(json, new TypeReference<Map<String, Object>>() {});

        assertEquals("Erik", result.get("name_"));

        Map<String, Object> addressMap = (Map<String, Object>) result.get("address_");
        assertEquals("Elvirastr", addressMap.get("street_"));
        Map<String, Object> cityMap = (Map<String, Object>) addressMap.get("city_");
        assertEquals(1, cityMap.get("id_"));
        assertEquals("Berlin", cityMap.get("name_"));
    }
}