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

Nulls.AS_EMPTY doesn't work for collections containing records

Open krzyk opened this issue 3 years ago • 1 comments

Describe the bug When deserializating an empty JSON object {} into a record that contains a list of another record results in:

java.lang.IllegalStateException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot create empty instance of [collection type; class java.util.List, contains [simple type, class com.example.Test$Foo$Bar]], no default Creator
 at [Source: (String)"{}"; line: 1, column: 1]

Version information 2.12.2

To Reproduce

   record Foo(List<Bar> list) {

        record Bar(String name, String value) {}
    }

    @Test
    public void test() {
        ObjectMapper mapper = new ObjectMapper().setDefaultSetterInfo(JsonSetter.Value.forContentNulls(Nulls.AS_EMPTY));
        try {
            mapper.readValue("{}", Foo.class);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException(e);
        }
    }

The above fails with:

java.lang.IllegalStateException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot create empty instance of [collection type; class java.util.List, contains [simple type, class com.example.Test$Foo$Bar]], no default Creator
 at [Source: (String)"{}"; line: 1, column: 1]

Expected behavior I would expect the deserialization give me an empty list.

krzyk avatar Mar 19 '21 11:03 krzyk

Any news on this? I recently just ran into the same issue

sagansfault avatar May 24 '22 22:05 sagansfault

@krzyk maybe should be using .forValueNulls(Nulls.AS_EMPTY) instead of .forContentNulls(Nulls.AS_EMPTY)?

Otherwise, can you please provide an example for JavaBeans (i.e. not Records) that actually works with .forContentNulls?

yihtserns avatar Jan 09 '23 18:01 yihtserns

@yihtserns You are correct: content nulls are only for Map entries and array/Collection elements; not for POJO/Record properties.

But the exception is strange at any rate: it should not be a problem to create empty List... (it gets mapped to ArrayList by default, uses default constructor).

cowtowncoder avatar Jan 11 '23 01:01 cowtowncoder

But the exception is strange at any rate: it should not be a problem to create empty List...

@cowtowncoder seems like it is complaining that Bar does not have no-arg constructor:

simple type, class com.example.Test$Foo$Bar

...although the error message does sound misleading. Same behaviour can be observed when using an equivalent non-Records class:

public static class Foo {

    private List<Bar> list;

    @JsonCreator
    public Foo(@JsonProperty("list") List<Bar> list) {
        this.list = list;
    }

    public static class Bar {

        private String name;
        private String value;

        @JsonCreator
        public Bar(@JsonProperty("name") String name, @JsonProperty("value") String value) {
            this.name = name;
            this.value = value;
        }
    }
}

The issue goes away by adding a no-arg constructor to Bar:

public static class Bar {

    private String name;
    private String value;

    @JsonCreator
    public Bar(@JsonProperty("name") String name, @JsonProperty("value") String value) {
        this.name = name;
        this.value = value;
    }

    public Bar() { // No more issue with this
    }
}

~~Unfortunately, adding a no-arg constructor (with/without @JsonCreator) for the Bar record class won't fix the issue (although it can work on #3724's branch).~~ UPDATE: Starting from 2.15 (due to #3724), adding no-arg constructor for the Bar record class will make the error go away.

yihtserns avatar Jan 11 '23 13:01 yihtserns

Ah. Ok, so the issue here is specifically that due to setting that would use "As.EMPTY" for elements of List, and since nominal type of List property is one that does not have zero-args constructor, things fail. This because in the event we did need "empty" Bar instance we'd have no way to construct one.

That is sort of valid reason to fail, potentially, if (but only if?) we ever needed to have empty Bar instance. Which here we don't.

I think there's an older issue mentioning the same problem wrt (overly) eager construction of handler to provide "empty" instance. But there is also then the question of deferring failure to a later point -- is that always desireable? Sometimes it'd be good to know about potential problem during construction of deserializer and not wait until specific piece of data triggers the condition.

For now it'd make sense to change configuration to use .forValueNulls(Nulls.AS_EMPTY), not .forContentNulls(...). Worth noting, too, is that @JsonSetter(...) annotation can be added to the property to override setting for List property as well.

cowtowncoder avatar Jan 12 '23 03:01 cowtowncoder