jackson-databind icon indicating copy to clipboard operation
jackson-databind copied to clipboard

Distinguish between null and non-set field when deserializing to Java 16 record

Open hajdamak opened this issue 2 years ago • 4 comments

In case of deserialization to Java 16 record is there any way to distinguish between situation when JSON field is set to null and when field is not set at all. For example using jackson-databind-nullable or Java's Optional ?

hajdamak avatar Jan 20 '22 15:01 hajdamak

This is not a user question forum: please use mailing list:

https://groups.google.com/g/jackson-user

or chat

https://gitter.im/FasterXML/jackson-databind

for asking about usage.

Or, if you have specific use case that does not way you'd expect, you could change this issue to include that. (use of Optional is supported for Record types just fine)

cowtowncoder avatar Jan 21 '22 02:01 cowtowncoder

@cowtowncoder Thanks for the info. Asked on the gitter. Note: This is not about whether Optional is supported in records or not.

hajdamak avatar Jan 21 '22 20:01 hajdamak

As discussed on Gitter: https://gitter.im/FasterXML/jackson-databind?at=61eb1a995dc6213cd4f3550e this seems not to be easily achievable. Therefore I believe this issue should stay as bug / request for extension. Here is minimal example that shows this problem: https://github.com/hajdamak/jackson-record-optional

hajdamak avatar Jan 28 '22 19:01 hajdamak

I am not 100% sure what the proposed solution would be here, so it would be good to outline that here.

I am guessing this might be to try to explicitly pass null for missing value for Optional<x> passed via Creator method; as opposed to Optional.empty() that'd be passed for explicit incoming JSON null. Is this what'd be expected?

Put another way, a unit test would make it easy to deduce intent.

cowtowncoder avatar Jan 30 '22 18:01 cowtowncoder

I'm not sure if this is a proper solution or a hack, but here's what I can think of:

public class App {

    public static final String NOT_PROVIDED_MARKER_ID = "__NOT_PROVIDED";

    public static void main(String[] args) throws Exception {
        record Car(String color, String type) {

            Car(@JacksonInject(NOT_PROVIDED_MARKER_ID) String color, String type) {
                this.color = color;
                this.type = type;
            }

            public boolean isColorProvided() {
                return !NOT_PROVIDED_MARKER_ID.equals(color);
            }

            @Override
            public String color() {
                return isColorProvided() ? color : null;
            }
        }

        ObjectMapper objectMapper = new ObjectMapper().setInjectableValues(new InjectableValues.Std().addValue(NOT_PROVIDED_MARKER_ID, NOT_PROVIDED_MARKER_ID));
        String[] jsons = {
                "{\"color\":null,\"type\":\"Solaris\"}",
                "{\"color\":\"Red\",\"type\":\"Solaris\"}",
                "{\"type\":\"Solaris\"}"
        };
        for (String json : jsons) {
            Car car = objectMapper.readValue(json, Car.class);
            System.out.printf("[%s] color '%s' was provided: %s\n", car.toString(), car.color(), car.isColorProvided());
        }
    }

Output:

[Car[color=null, type=Solaris]] color 'null' was provided: true
[Car[color=Red, type=Solaris]] color 'Red' was provided: true
[Car[color=__NOT_PROVIDED, type=Solaris]] color 'null' was provided: false

Caveat: Your car.toString() will show color as __NOT_PROVIDED.

yihtserns avatar Jan 14 '23 15:01 yihtserns

I am guessing this might be to try to explicitly pass null for missing value for Optional passed via Creator method; as opposed to Optional.empty() that'd be passed for explicit incoming JSON null.

Having an optional == null seems to be going against the idea of Optional... 😰

yihtserns avatar Jan 14 '23 15:01 yihtserns

I'm assuming you're currently doing something like this using non-Records class:

public static class Car {

    private boolean colorProvided = false;
    private String color;
    ...

    public void setColor(String color) {
        this.colorProvided = true;
        this.color = color;
    }
    ...

    public boolean isColorProvided() {
        return colorProvided;
    }
}

If so, I think you should just stick to that. Trying to fit this special scenario into Records feels like trying to fit square peg in a round hole.

yihtserns avatar Jan 14 '23 15:01 yihtserns