Skip to content

StdDelegatingSerializer does not consider a Converter that may return null for a non-null input #4575

Closed
@plevart

Description

@plevart

Search before asking

  • I searched in the issues and found nothing similar.

Describe the bug

I have a custom NullModule extends SimpleModule that installs serializer and deserializer modifiers:

public NullModule() {
        setSerializerModifier(new NullSerializerModifier());
        setDeserializerModifier(new NullDeserializerModifier());
}

the NullSerializerModifier wraps each JsonSerializer with a subclass of StdDelegatingSerializer using a Converter that may return null for some special non-null instances of values to be serialized. I use this together with serializationInclusion(JsonInclude.Include.NON_NULL) configuration of ObjectMapper to suppress serialization of bean attributes with null values, but still allow selective serialization of JSON null values for attributes that are assigned these special non-null instancess (an alternative to JsonNullable wrapper class from openapi-tools but without ugly wrapper class). This works perfectly but recently I found a problem since I wanted to serialize a special instance of a type that represents a root of hierarchy of types for the first time. It works correctly for types that don't represent a hierarchy of types since in this case the following StdDelegatingSerializer method is invoked:

    public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException
    {
        Object delegateValue = convertValue(value);
        // should we accept nulls?
        if (delegateValue == null) {
            provider.defaultSerializeNull(gen);
            return;
        }
        // 02-Apr-2015, tatu: As per [databind#731] may need to do dynamic lookup
        JsonSerializer<Object> ser = _delegateSerializer;
        if (ser == null) {
            ser = _findSerializer(delegateValue, provider);
        }
        ser.serialize(delegateValue, gen, provider);
    }

As you can see, the value to be serialized is 1st converted to delegateValue via provided Converter and then a special case is considered when this converted value is null. In that case, null serialized value is emitted.

But in case the type of serialized value represents a hierarchy of types, the other StdDelegatingSerializer method is invoked:

    public void serializeWithType(Object value, JsonGenerator gen, SerializerProvider provider,
            TypeSerializer typeSer) throws IOException
    {
        // 03-Oct-2012, tatu: This is actually unlikely to work ok... but for now,
        //    let's give it a chance?
        Object delegateValue = convertValue(value);
        JsonSerializer<Object> ser = _delegateSerializer;
        if (ser == null) {
            ser = _findSerializer(value, provider);
        }
        ser.serializeWithType(delegateValue, gen, provider, typeSer);
    }

This method, OTOH, does not consider a special case that delegateValue might be null which causes a NullPointerException to be thrown later when this null value is attempted to be used for extracting the type ID from.

Currently my work-around is to override method serializeWithType in my custom StdDelegatingSerializer subclass and reimplement it with this special-case check inserted exactly at the same place as in the serialize method above.

Would you consider this a bug?

Regards, Peter

Version Information

2.17.0

Reproduction

Can be reproduced by running a unit test provided in the fix without the actual fix applied. The following is printed:

[ERROR] com.fasterxml.jackson.databind.ser.TestCustomSerializers.testIssue4575 -- Time elapsed: 0.005 s <<< ERROR!
com.fasterxml.jackson.databind.JsonMappingException: Cannot invoke "Object.getClass()" because "value" is null
        at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._wrapAsIOE(DefaultSerializerProvider.java:531)
        at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:504)
        at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:341)
        at com.fasterxml.jackson.databind.ObjectMapper._writeValueAndClose(ObjectMapper.java:4811)
        at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:4052)
        at com.fasterxml.jackson.databind.ser.TestCustomSerializers.testIssue4575(TestCustomSerializers.java:397)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "value" is null
        at com.fasterxml.jackson.databind.jsontype.impl.TypeNameIdResolver.idFromValue(TypeNameIdResolver.java:115)
        at com.fasterxml.jackson.databind.jsontype.impl.TypeSerializerBase.idFromValue(TypeSerializerBase.java:92)
        at com.fasterxml.jackson.databind.jsontype.impl.TypeSerializerBase._generateTypeId(TypeSerializerBase.java:77)
        at com.fasterxml.jackson.databind.jsontype.impl.TypeSerializerBase.writeTypePrefix(TypeSerializerBase.java:45)
        at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeWithType(BeanSerializerBase.java:648)
        at com.fasterxml.jackson.databind.ser.std.StdDelegatingSerializer.serializeWithType(StdDelegatingSerializer.java:186)
        at com.fasterxml.jackson.databind.ser.impl.TypeWrappedSerializer.serialize(TypeWrappedSerializer.java:32)
        at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider._serialize(DefaultSerializerProvider.java:502)
        ... 7 more

Expected behavior

I would expect the StdDelegatingSerializer to behave consistently for types that do and types that don't represent a type hierarchy.

Additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    2.17Issues planned at earliest for 2.17

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions