Skip to content

When serializing a Map using the serializer obtained with SerializerProvider.findValueSerializer, a NullPointerException is thrown due to missing key serializer #4928

Closed as not planned
@k163377

Description

@k163377

Search before asking

  • I searched in the issues and found nothing similar.

Describe the bug

This issue is a continuation of #4878 (FasterXML/jackson-module-kotlin#873).
There was a misunderstanding about reproducing the bug in Java, and the test case seems to have detected another similar bug.

Version Information

Confirmed on 2.18 and branches.

Reproduction

package com.fasterxml.jackson.databind;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.module.SimpleSerializers;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.junit.jupiter.api.Test;

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

public class Kotlin873Test {
    // region: DTO Definitions
    static class MapWrapper {
        private final Map<String, Object> value;

        MapWrapper(Map<String, Object> value) {
            this.value = value;
        }

        public Map<String, Object> getValue() {
            return value;
        }
    }
    // endregion

    // region: Jackson settings
    static class UnboxSerializer extends StdSerializer<MapWrapper> {
        UnboxSerializer() {
            super(MapWrapper.class);
        }

        @Override
        public void serialize(MapWrapper value, JsonGenerator gen, SerializerProvider provider) throws IOException {
            Map<String, Object> unboxed = value.getValue();
            unboxed.put("unboxed", true);

            provider.findValueSerializer(unboxed.getClass()).serialize(unboxed, gen, provider);
        }
    }

    static class MySerializers extends SimpleSerializers {
        @Override
        public JsonSerializer<?> findSerializer(SerializationConfig config, JavaType type, BeanDescription beanDesc) {
            Class<?> rawClass = type.getRawClass();
            if (MapWrapper.class.isAssignableFrom(rawClass)) {
                return new UnboxSerializer();
            }
            return super.findSerializer(config, type, beanDesc);
        }
    }

    private final ObjectMapper mapper;

    public Kotlin873Test() {
        SimpleModule sm = new SimpleModule() {
            @Override
            public void setupModule(SetupContext context) {
                super.setupModule(context);

                context.addSerializers(new MySerializers());
            }
        };
        sm.setSerializers(new MySerializers());
        mapper = new ObjectMapper().registerModule(sm);
    }
    // endregion

    @Test
    public void test() throws JsonProcessingException {
        Map<String, Object> map = new HashMap<>();
        map.put("a", 1);

        System.out.println(mapper.writeValueAsString(new MapWrapper(map)));
    }
}

Expected behavior

At least it shouldn't be a NullPointerException.

Additional context

Finally, if the following tests succeed, the problem in kotlin-module should be solved.

package com.fasterxml.jackson.databind;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.module.SimpleSerializers;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.databind.util.StdConverter;
import org.junit.jupiter.api.Test;

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

public class Kotlin873Test {
    // region: DTO Definitions
    static class MapWrapper {
        private final Map<String, Object> value;

        MapWrapper(Map<String, Object> value) {
            this.value = value;
        }

        public Map<String, Object> getValue() {
            return value;
        }
    }

    static class Dto {
        private final Map<String, Object> value;

        Dto(Map<String, Object> value) {
            this.value = value;
        }

        public Map<String, Object> getValue() {
            return value;
        }
    }
    // endregion

    // region: Jackson settings
    static class BoxConverter extends StdConverter<Map<String, Object>, MapWrapper> {
        @Override
        public MapWrapper convert(Map<String, Object> value) {
            return new MapWrapper(value);
        }
    }

    static class UnboxSerializer extends StdSerializer<MapWrapper> {
        UnboxSerializer() {
            super(MapWrapper.class);
        }

        @Override
        public void serialize(MapWrapper value, JsonGenerator gen, SerializerProvider provider) throws IOException {
            Map<String, Object> unboxed = value.getValue();
            unboxed.put("unboxed", true);

            provider.findValueSerializer(unboxed.getClass()).serialize(unboxed, gen, provider);
        }
    }

    static class MySerializers extends SimpleSerializers {
        @Override
        public JsonSerializer<?> findSerializer(SerializationConfig config, JavaType type, BeanDescription beanDesc) {
            Class<?> rawClass = type.getRawClass();
            if (MapWrapper.class.isAssignableFrom(rawClass)) {
                return new UnboxSerializer();
            }
            return super.findSerializer(config, type, beanDesc);
        }
    }

    static class AI extends NopAnnotationIntrospector {
        @Override
        public Object findSerializationConverter(Annotated a) {
            if (a instanceof AnnotatedMethod) {
                return new BoxConverter();
            }

            return null;
        }
    }

    private final ObjectMapper mapper;

    public Kotlin873Test() {
        SimpleModule sm = new SimpleModule() {
            @Override
            public void setupModule(SetupContext context) {
                super.setupModule(context);

                context.addSerializers(new MySerializers());
                context.appendAnnotationIntrospector(new AI());
            }
        };
        sm.setSerializers(new MySerializers());
        mapper = new ObjectMapper().registerModule(sm);
    }
    // endregion

    @Test
    public void test() throws JsonProcessingException {
        Map<String, Object> map = new HashMap<>();
        map.put("a", 1);

        Dto dto = new Dto(map);

        String json = mapper.writeValueAsString(dto);
        System.out.println(json);
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    will-not-fixClosed as either non-issue or something not planned to be worked on

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions