Repository navigation
When serializing a Map using the serializer obtained with SerializerProvider.findValueSerializer, a NullPointerException is thrown due to missing key serializer #4928
Copy link
Copy link
Closed as not planned
Labels
will-not-fixClosed as either non-issue or something not planned to be worked onClosed as either non-issue or something not planned to be worked on
Description
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);
}
}Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
will-not-fixClosed as either non-issue or something not planned to be worked onClosed as either non-issue or something not planned to be worked on
Activity
JooHyukKim commentedon Jan 26, 2025
@k163377 What's the difference between the two reproductions? One under "reproduction" and one under "addtional context" 👀
k163377 commentedon Jan 26, 2025
@JooHyukKim
What I wrote in the reproduction section is a
MVCEof this issue.What I wrote in the additional context section is a pseudo-reproduction of the
value classserialization process inkotlin-module, which I eventually want to solve.cowtowncoder commentedon Jan 26, 2025
I think reproduction has a bug, this can not work:
Since
provider.findValueSerializer()returns non-contextualized instance -- it MUST be contextualized, or different method called to find contextualized serializer (findPrimaryPropertySerializer(), passingnullBeanProperty).Or, perhaps better yet call:
Unfortunately I do not think there is a good way to handle things more gracefully here: adding
nullchecks for invalid state is kind of defensive coding I don't really like.But I will see if Javadoc note on
findValueSerializer()could be added.Javadoc improvement wrt #4928
cowtowncoder commentedon Jan 26, 2025
So, basically, handling by caller needs to be changed: no new bug demonstrated.
k163377 commentedon Feb 2, 2025
@cowtowncoder
Sorry for the late reply.
Thank you, I have confirmed that this fixes the
kotlin-moduleissue.FasterXML/jackson-module-kotlin#904