I figured out what I was doing wrong, and it was very simple, I just got stuck: I wasn't using readValue
for Deserialization, but instead convertValue
.
As the javadoc of convertValue
mentions:
This method is functionally similar to first serializing given value into JSON, and then binding JSON data into value of given type
So since the serializationInclusion
was set to skip nulls on output, the field was being removed completely in the intermediate step.
Reproduction example:
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.junit.Test;
import java.util.HashMap;
import java.util.Optional;
public class JacksonOptionalTest {
@Test
public void testOptional() throws JsonProcessingException {
var OBJECT_MAPPER = JsonMapper.builder()
.addModule(new Jdk8Module())
.serializationInclusion(JsonInclude.Include.NON_ABSENT)
.build();
System.out.println("writeValueAsString");
System.out.println(OBJECT_MAPPER.writeValueAsString(new MyClass(null)));
System.out.println(OBJECT_MAPPER.writeValueAsString(new MyClass(Optional.empty())));
System.out.println(OBJECT_MAPPER.writeValueAsString(new MyClass(Optional.of("jim"))));
System.out.println("\nreadValue");
System.out.println(OBJECT_MAPPER.readValue("{}", MyClass.class).toString());
System.out.println(OBJECT_MAPPER.readValue("{\"something\":null}", MyClass.class).toString());
System.out.println(OBJECT_MAPPER.readValue("{\"something\":\"jim\"}", MyClass.class).toString());
final var map = new HashMap<>();
map.put("something", null);
System.out.println("\nconvertValue");
System.out.println(OBJECT_MAPPER.convertValue(map, MyClass.class).toString());
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
private static class MyClass {
@JsonProperty
private Optional<String> something;
}
}
If you run the above test, it prints the following:
writeValueAsString
{}
{}
{"something":"jim"}
readValue
JacksonOptionalTest.MyClass(something=null)
JacksonOptionalTest.MyClass(something=Optional.empty)
JacksonOptionalTest.MyClass(something=Optional[jim])
convertValue
JacksonOptionalTest.MyClass(something=null)
In the last test with convertValue
we can see that instead of deserializing the value to Optional.empty
, we got null, which is expected according to what convertValue
does.