5

With this data model...

TestClass.kt

data class TestClass (val bar: Optional<Double>?)

My goal is to deserialize the following json values as such:

{"foo": 3.5}  --> foo = 3.5
{"foo": null} --> foo = Optional.empty() // This is currently my problem. foo is null and I can't seem to fix it
{}            --> foo = null

I've seen the solution here and tried this, but the breakpoints in my "deserialize" method never seem to hit.

OptionalDeserializer.java

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import java.io.IOException;
import java.util.Optional;

public class OptionalDeserializer extends JsonDeserializer<Optional<?>> implements ContextualDeserializer {
    private JavaType valueType;

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext context, BeanProperty property) {
        this.valueType = property.getType().containedType(0);
        return this;
    }

    @Override
    public Optional<?> deserialize(final JsonParser parser, final DeserializationContext context) throws IOException {
        final JsonNode node = parser.getCodec().readTree(parser);

        return node.isNull()
                ? Optional.empty()
                : Optional.of(context.readValue(parser, valueType));
    }

}

TestDeserialization.kt

fun main(): {
    val objectMapper = ObjectMapper().registerModule(KotlinModule())

    val module = SimpleModule()
    module.addDeserializer(Optional::class.java, OptionalDeserializer())
    objectMapper.registerModule(module)

    objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)

    objectMapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true)
    objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true)
    objectMapper.nodeFactory = JsonNodeFactory.withExactBigDecimals(true)

    val inputJson = """{"foo" : null}"""

    val expectedObject = TestClass(foo = Optional.empty())

    val actualObject = objectMapper.readValue(inputJson, TestClassBravo::class.java)

    assertEquals(expectedObject, actualObject)
}

build.gradle (for version info)

compile 'com.fasterxml.jackson.core:jackson-databind:2.9.8'
compile 'com.fasterxml.jackson.module:jackson-module-kotlin:2.9.8'
A Frayed Knot
  • 476
  • 5
  • 20
  • IMO, `Optional?` is a code smell. Consider changing this class and the problem may go. – madhead Feb 13 '20 at 12:30
  • Oops. Accidentally left Decimal in there. My end goal is to distinguish between null and missing properties, and ultimately recreate the input json exactly. So as “smelly” as it may seem, it’s the only reasonable solution I’ve found. – A Frayed Knot Feb 13 '20 at 14:59

1 Answers1

0

There are two ways you can handle this.

  1. It would be easier not to use a custom serializer, but rely on the jackson default Jdk8Module (see here)

  2. If you want your custom Deserializer, you need to specify the null access pattern, else the deserializer will not be called for null values:

Code for 2:

// In OptionalDeserializer

@Override
public AccessPattern getNullAccessPattern() {
    return AccessPattern.CONSTANT;
}
sfiss
  • 2,119
  • 13
  • 19
  • I tried both solutions. Unfortunately the Jdk8Module doesn't work with Kotlin data classes. I tried it with and without adding the KotlinModule. Works great for Java classes, but I have a domain model with about 50 classes and I wanted to take advantage of Kotlin's data class features. As for option 2, I tried it but my overridden method `getNullAccessPattern` was never even called. – A Frayed Knot Feb 19 '20 at 04:34