FasterXML / jackson-databind

General data-binding package for Jackson (2.x): works on streaming API (core) implementation(s)
Apache License 2.0
3.52k stars 1.38k forks source link

The `KeyDeserializer` specified in the class with `@JsonDeserialize(keyUsing = ...)` is overwritten by the `KeyDeserializer` specified in the `ObjectMapper`. #4444

Open k163377 opened 7 months ago

k163377 commented 7 months ago

Search before asking

Describe the bug

SSIA

Also, the attached Java reproduction code is directly adding KeyDeserializer to SimpleModule, but it seemed to be reproduced when using KeyDeserializers.

Version Information

Reproduced in the latest 2.17 branch(fe42cf7). Also, 2.16.1 seems to have the same problem.

Reproduction

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.util.Map;

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

public class KeyDeserializerOverwritten {
    @JsonDeserialize(keyUsing = ForClass.class)
    static class MyKey {
        private final String value;

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

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

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

    TypeReference<Map<MyKey, String>> typeRef = new TypeReference<>() {};

    @Test
    void notCustom() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        Map<MyKey, String> result = mapper.readValue("{\"foo\":null}", typeRef);

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

    @Test
    void addKeyDeserializer() throws JsonProcessingException {
        SimpleModule sm = new SimpleModule();
        sm.addKeyDeserializer(MyKey.class, new ForMapper());

        ObjectMapper mapper = new ObjectMapper().registerModule(sm);
        Map<MyKey, String> result = mapper.readValue("{\"foo\":null}", typeRef);

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

Expected behavior

Like JsonDeserializer, the KeyDeserializer specified for the class must be used.

Additional context

This problem was discovered during prototyping to solve https://github.com/FasterXML/jackson-module-kotlin/issues/777. https://github.com/ProjectMapK/jackson-module-kogera/pull/224

I'm not sure if I should merge it into kotlin-module as it is, since the default content provided by KotlinModule overrides any user customization.

cowtowncoder commented 7 months ago

Sounds like a bug indeed.