2

I'm trying to serialize the Java object with hashmap into json string using jackson object mapper.

Below is the class definition of Ext -

        import com.fasterxml.jackson.annotation.JsonAnyGetter;
        import com.fasterxml.jackson.annotation.JsonAnySetter;
        import com.fasterxml.jackson.annotation.JsonIgnore;
        import com.fasterxml.jackson.annotation.JsonInclude;
        import com.fasterxml.jackson.annotation.JsonPropertyOrder;
        import com.fasterxml.jackson.annotation.JsonInclude.Include;
        import java.io.Serializable;
        import java.util.HashMap;
        import java.util.Map;
        import java.util.Objects;

        @JsonInclude(Include.NON_NULL)
        @JsonPropertyOrder({})
        public class Ext implements Serializable {

            @JsonIgnore
            private Map<String, Object> additionalProperties = new HashMap();
            private static final long serialVersionUID = -4500317258794294335L;

            public Ext() {
            }

            @JsonAnyGetter
            public Map<String, Object> getAdditionalProperties() {
                return this.additionalProperties;
            }

            @JsonAnySetter
            public void setAdditionalProperty(String name, Object value) {
                this.additionalProperties.put(name, value);
            }

            // ignore toString, equals and hascode

            public static class ExtBuilder {
                protected Ext instance;

                public ExtBuilder() {
                    if (this.getClass().equals(Ext.ExtBuilder.class)) {
                        this.instance = new Ext();
                    }
                }

                public Ext build() {
                    Ext result = this.instance;
                    this.instance = null;
                    return result;
                }

                public Ext.ExtBuilder withAdditionalProperty(String name, Object value) {
                    this.instance.getAdditionalProperties().put(name, value);
                    return this;
                }
            }
        }

Below is the the sample test case -

    @Test
    public void testNullObjectSerialization() throws Exception {

        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
        mapper.setDefaultPropertyInclusion(
              JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL));

        ByteArrayOutputStream out = new ByteArrayOutputStream(1000);
        mapper.writeValue(out, new Ext.ExtBuilder().withAdditionalProperty("unexpected", null).withAdditionalProperty("expected", true).build());
        String result = new String(out.toByteArray());
        Assert.assertEquals("{\"expected\":true}", result);
    }

I used

mapper.setDefaultPropertyInclusion(
              JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL));` 

by using the answer provided in stack overflow question.

I'm expecting the result as {"expected":true} but somehow the key with null value is included in the result.

How can I fix this issue?

Note:

  1. Code is on github here.
  2. I'm using jacksersion version 2.9.8
user51
  • 8,843
  • 21
  • 79
  • 158
  • can you remove this `JsonInclude.Include.ALWAYS` and try – Ryuzaki L Jan 24 '20 at 20:36
  • If I remove `JsonInclude.Include.ALWAYS`, I'm getting compiler error `Cannot resolve method 'construct(com.fasterxml.jackson.annotation.JsonInclude.Include)'`. – user51 Jan 24 '20 at 20:39
  • I tried with both JsonInclude.Include.NON_NULL `mapper.setDefaultPropertyInclusion( JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL));` Still same issue – user51 Jan 24 '20 at 20:40
  • I'm guessing it was because of `Map` jackson could not able to find the map properties with null – Ryuzaki L Jan 24 '20 at 20:46

2 Answers2

1

When you put @JsonInclude on a field, that means if the field (not the element of fields) is null then it will be ignored from conversion.

  • In your case when you use @JsonInclude on additionalProperties that means, if additionalProperties(itself) is null then it will be ignored from conversion.

so @JsonInclude only checks additionalProperties itself for null value, not its elements.


Example: Assume that you have an Ext object with additionalProperties=null, when you want to serialize it, the additionalProperties is ignored because it is null.

  • But in your scenario the elements of the additionalProperties map contain null and Jackson doesn't ignore them.

Solution 1

You can serialize additionalProperties map (itself) not the whole Ext object.

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

        //Create Ext  Object
        Ext ext = new Ext.ExtBuilder().withAdditionalProperty("unexpected", null).withAdditionalProperty("expected", true).build();

        //Config
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        //Conversion the additionalProperties map (ext.getAdditionalProperties())
        String filterMap = mapper.writeValueAsString(ext.getAdditionalProperties());

        //Result ({"expected":true})
        System.out.println(filterMap);


    }
}        

Convert JSON to Ext Object

And also you can get Ext object from the generated string. in this case, your object has a filtered additionalProperties (does not have any element with null key or null value)

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

        //Create Ext  Object
        Ext ext = new Ext.ExtBuilder().withAdditionalProperty("unexpected", null).withAdditionalProperty("expected", true).build();

        //Config
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        //Conversion the additionalProperties map (ext.getAdditionalProperties())
        String filterMap = mapper.writeValueAsString(ext.getAdditionalProperties());

        //Result ({"expected":true})
        System.out.println(filterMap);

        //Convert back JSON string to Ext object
        Ext filterdExt = mapper.readValue(filterMap, Ext.class);

        //Print AdditionalProperties of filterdExt ({"expected":true})
        System.out.println(filterdExt.getAdditionalProperties());

    }

Solution 2

You can write a custom serializer.

This link might be useful for this purpose

0

WriteValue does not consider inclusion in any way: it only applies to writing of JSON. Conversion is the sequence of write-then-read; but by time read is done, output does not contain excluded values any more.

Replace this code:

ByteArrayOutputStream out = new ByteArrayOutputStream(1000);
        mapper.writeValue(out, new Ext.ExtBuilder().withAdditionalProperty("unexpected", null).withAdditionalProperty("expected", true).build());
        String result = new String(out.toByteArray());
        Assert.assertEquals("{\"expected\":true}", result);

by

ByteArrayOutputStream out = new ByteArrayOutputStream(1000);
            mapper.writeValue(out, mapper.convertValue(new Ext.ExtBuilder().withAdditionalProperty("unexpected", null).withAdditionalProperty("expected", true).build(), Ext.class));

            String result = new String(out.toByteArray());
            Assert.assertEquals("{\"expected\":true}", result);
mdev
  • 349
  • 1
  • 5