javajsonjacksonjackson-databind

Jackson Deserialization results in duplicate Values in List field when additional getter is present


I am trying to get my head around the issue where Jackson de-serialization to object results in the list field having duplicate class, when we added an extra getter of the list field.

Here are the classes

public class Wrapper {

    private List<String> values;

    public List<String> getAllValues() {
        if (values == null) {
            values = new ArrayList<>();
        }
        return values;
    }

    public void setValues(final List<String> values) {
        this.values = values;
    }

    public List<String> getValues() {
        return values;
    }

    public String toString() {
        return MoreObjects.toStringHelper(this).add("values", values).toString();
    }
}

My json that I am trying to de-serialize is this:

{
    "values": [
        "value1"
    ],
    "allValues": [
        "value1"
    ]
}

Now, as per my understanding, the setter is only present for values, so I am expecting the allValues entry in the JSON to be ignored since I don't have any setter there. It is just a getter field introduced for transformation reason.

But apparently, Jackson is not ignoring the field, but using it to set it on the values list.

Below is the code which confirms the duplicate entries during de-serialization:

public static void main(String[] args) throws Exception {

    final ObjectMapper mapper = new ObjectMapper();

    final String wrapperJson = "{ \"values\": [ \"value1\" ], \"allValues\": [\"value1\"] }";

    System.out.println(mapper.readValue(wrapperJson.getBytes(), Wrapper.class));
}

The output printed is this:

Wrapper{values=[value1, value1]}

Can somebody explain how Jackson is creating the duplicate entry?

Additional information - We are using Jackson Databind - 2.13.5 (Yes, it's an old one, so it may be possible this is already fixed with the new versions)


Solution

  • The reason why your fields are serialized and deserialized twice is because of Jackson's serialization and deserialization rules. Jackson uses getters to both serialize and deserialize, while, if getters are absent, it relies on setters to only deserialize fields. Here is a great article from Baeldung on Jackson serialization and deserialization rules.

    In your case, since you have two getters that basically return the same attribute (values), the property is being serialized twice. On the other hand, during deserialization, since Jackson uses getters in this instance too and knows that values is returned for both getAllValues() and getValues(), the first Json element is set via reflection by referring the attribute returned by getValues(), while the second Json element is set, still via reflection, by referring the attribute returned by getAllValues(), which is still values.

    To avoid this double serialization/deserialization, you should add the @JsonIgnore annotation before one of the two getters, for example getAllValues().

    public class Wrapper {
    
        private List<String> values;
    
        @JsonIgnore
        public List<String> getAllValues() {
            if (values == null) {
                values = new ArrayList<>();
            }
            return values;
        }
    
        public void setValues(final List<String> values) {
            this.values = values;
        }
    
        public List<String> getValues() {
            return values;
        }
    
        public String toString() {
            return MoreObjects.toStringHelper(this).add("values", values).toString();
        }
    }