Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@JsonSetter(nulls = Nulls.SKIP) doesn't work in some situations #4441

Closed
1 task done
Asapin opened this issue Mar 22, 2024 · 7 comments
Closed
1 task done

@JsonSetter(nulls = Nulls.SKIP) doesn't work in some situations #4441

Asapin opened this issue Mar 22, 2024 · 7 comments
Labels
2.17 Issues planned at earliest for 2.17
Milestone

Comments

@Asapin
Copy link

Asapin commented Mar 22, 2024

Search before asking

  • I searched in the issues and found nothing similar.

Describe the bug

We're using @JsonSetter(nulls = Nulls.SKIP) quite heavily in our code base to avoid dealing with null values, but yesterday I noticed that some fields contain null despite being annotated with @JsonSetter(nulls = Nulls.SKIP)

Version Information

2.15.3, 2.15.4, 2.16.0, 2.16.1, 2.16.2, 2.17.0

Reproduction

public class Main {
    static class Outer {
        @JsonSetter(nulls = Nulls.SKIP)
        private final List<Middle> list1 = new ArrayList<>();

        public Outer() {
        }

        public List<Middle> getList1() {
            return list1;
        }
    }

    static class Middle {
        @JsonSetter(nulls = Nulls.SKIP)
        private final List<Inner> list1 = new ArrayList<>();
        private final String field1;

        @ConstructorProperties({"field1"})
        public Middle(String field1) {
            this.field1 = field1;
        }

        public List<Inner> getList1() {
            return list1;
        }

        public String getField1() {
            return field1;
        }
    }

    static class Inner {
        private final String field1;

        @ConstructorProperties({"field1"})
        public Inner(String field1) {
            this.field1 = field1;
        }

        public String getField1() {
            return field1;
        }
    }

    public static void main(String[] args) {
        String json = """
                {
                    "list1": [
                        {
                            "list1": null,
                            "field1": "data"
                        }
                    ]
                }
                """;
        ObjectMapper objectMapper = new ObjectMapper();
        Outer outer;
        try {
            outer = objectMapper.readValue(json, Outer.class);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        validateNotNull(outer);
        validateNotNull(outer.getList1());
        for (Middle middle : outer.getList1()) {
            validateNotNull(middle);
            validateNotNull(middle.getField1());
            validateNotNull(middle.getList1());
        }
    }

    private static void validateNotNull(Object o) {
        if (o == null) {
            throw new IllegalStateException("Shouldn't be null");
        }
    }
}

Expected behavior

middle.getList1() shouldn't be null since it's annotated with @JsonSetter(nulls = Nulls.SKIP)

Additional context

Any of the following seems to fix the issue, but is not really feasible to do:

  • Change the order of fields in the JSON:
{
    "list1": [
        {
            "field1": "data",
            "list1": null
        }
    ]
}
  • Remove final from Middle#field1 and remove this field from constructor parameters
@Asapin Asapin added the to-evaluate Issue that has been received but not yet evaluated label Mar 22, 2024
@JooHyukKim
Copy link
Member

Sorry late asking, but are the versions in "Version Informations" all that are not working?

@Asapin
Copy link
Author

Asapin commented Mar 25, 2024

Yes, these are versions where the bug happens (didn't test older versions though).

I first discovered this issue when using 2.15.3. Later I tried newer versions hoping that this was fixed, but the issue was still reproducible with all newer versions as well.

@JooHyukKim
Copy link
Member

Sorry late again @Asapin, one more question is which latest version did it work? 2.15.2? 👈🏼 Finding this out would help alot.

I modified a bit around visibility of fields (and simplified a bit), it seems to work. If urgent, I guess possible work around would be modifying visilbity via mapper.setVisibility()? Maybe others will know more.

public class BeanDeserializerModifier4216Test {
    static class Outer {
        @JsonSetter(nulls = Nulls.SKIP)
        public List<Middle> list1 = new ArrayList<>();
    }

    static class Middle {
        @JsonSetter(nulls = Nulls.SKIP)
        public List<Inner> list1 = new ArrayList<>();
        public String field1;
    }

    static class Inner {
        private final String field1;

        @ConstructorProperties({"field1"})
        public Inner(String field1) {
            this.field1 = field1;
        }

        public String getField1() {
            return field1;
        }
    }

    public static void main(String[] args) {
        try {
            Outer outer = new ObjectMapper().readValue("""
                    {
                        "list1": [
                            {
                                "list1": null,
                                "field1": "data"
                            }
                        ]
                    }""", Outer.class);
            validateNotNull(outer);
            validateNotNull(outer.list1);
            for (Middle middle : outer.list1) {
                validateNotNull(middle);
                validateNotNull(middle.field1);
                validateNotNull(middle.list1);
            }
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

    private static void validateNotNull(Object o) {
        if (o == null) {
            throw new IllegalStateException("Shouldn't be null");
        }
    }
}

@Asapin
Copy link
Author

Asapin commented Mar 27, 2024

@JooHyukKim
I don't think it ever worked before. I tried the following versions: 2.15.2, 2.15.1, 2.15.0, 2.14.0, 2.13.0, 2.12.0, 2.11.0, 2.10.0 and 2.9.0, and the all failed this test, but they all work if I change the order of fields in the JSON

@JooHyukKim
Copy link
Member

Strange... seems like bug. If you can either...

  • change the order of fields in the JSON
  • or change modifier as I suggested above

as a temporary work-around, that'd be great 👍🏼

@cowtowncoder
Copy link
Member

Changing order might suggest the issue has something to do with buffering (as buffering only needed in some cases depending on input order of content).

@cowtowncoder
Copy link
Member

Ahhh. Actually, it is sort of due to buffering... but quite indirectly. @JooHyukKim figured it out; it's missing logic from set() methods of FieldProperty and MethodProperty.

@cowtowncoder cowtowncoder changed the title @JsonSetter(nulls = Nulls.SKIP) doesn't work in some situations @JsonSetter(nulls = Nulls.SKIP) doesn't work in some situations Apr 7, 2024
@cowtowncoder cowtowncoder added 2.17 Issues planned at earliest for 2.17 and removed to-evaluate Issue that has been received but not yet evaluated labels Apr 7, 2024
@cowtowncoder cowtowncoder added this to the 2.17.1 milestone Apr 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2.17 Issues planned at earliest for 2.17
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants