Jackson: how to exclude null value properties from JSON serialization

In the comments to a previous article about the Jackson library, it had been raised the question on how to exclude from the JSON serialization of a Java object properties that had a null value. Since it was not the first time I received a request for clarification on the subject, I decided to deal with it in detail in this new post.

Jackson offers two main alternatives to exclude null values from JSON serialization:

  • At single class level, using the JsonInclude annotation with the value Include.NON_NULL
  • At ObjectMapper level using the setSerializationInclusion (JsonInclude.Include.NON_NULL)

Using the first method we act at class level, while with the second method we set the behavior globally making it valid for all classes serialized using that ObjectMapper. Here is an example of the application of these two strategies, based on classes already used in this previous article where we had analyzed in detail the JSON serialization and serialization processes. So, we’re going to reuse classes like Order, OrderItem, Customer, etc .. with which we represented, in a simplified way, an orders management system:

The Customer class is defined as follows:

public class Customer {

    private long id;
    private String firstName;
    private String lastName;

    @JsonCreator
    public Customer(@JsonProperty("id")long id, @JsonProperty("firstName")String fn, @JsonProperty("lastName")String ln) {
        this.firstName = fn;
        this.lastName = ln;
        this.id = id;
    }

	// GETTERS AND SETTERS OMITTED

    @Override
    public String toString() {
            return this.firstName + " " + this.lastName;
    }
}

The Order class is defined as follows:

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;

public class Order {

    private long id;
    private Customer customer;
    private List<OrderItem> itemList;
    private double total;

    @JsonCreator
    public Order(@JsonProperty("id") long id, @JsonProperty("cust") Customer cust, @JsonProperty("itemList")List<OrderItem> li, @JsonProperty("placedDate")Date pDate) {
        this.id = id;
        this.customer = cust;
        for (OrderItem oi : li) {
            this.addItemToOrder(oi);
        }
        this.placedDate = pDate;
    }


    public Order(long id, Customer cust, List<OrderItem> li, Date pDate, double total) {
        this.id = id;
        this.customer = cust;
        this.itemList = li;
        this.placedDate = pDate;
        this.total = this.total;
    }

	// GETTERS AND SETTERS OMITTED
	
    @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd,HH:mm", timezone="CET")
    private Date placedDate;

    public void addItemToOrder(OrderItem oi) {
        if (this.itemList == null) {
            this.itemList = new ArrayList<>();
        }
        this.itemList.add(oi);
        if (oi != null) {
            this.total += oi.getPrice();
        }
    }

    @Override
    public String toString() {
        StringBuilder ret = new StringBuilder();
        ret.append("ORDER ID: ").append(this.id).append("\n");
        ret.append("ORDER DATE: ").append(this.placedDate).append("\n");
        ret.append("CUSTOMER: ").append(this.customer).append("\n");
        ret.append("ITEMS:\n");
        for (OrderItem oi : this.itemList) {
            ret.append("\t").append(oi).append("\n");
        }
        ret.append("TOTAL: ").append(this.total).append("\n");
        return ret.toString();
    }
}

The OrderItem class is defined as follows:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class OrderItem {

    private int quantity;
    private Item it;
    private double price;

    @JsonCreator
    public OrderItem(@JsonProperty("quantity")int q, @JsonProperty("it")Item it, @JsonProperty("price")double p){
        this.quantity = q;
        this.it = it;
        this.price = p;
    }

    public OrderItem(int q, Item it) {
        this(q, it, q * it.getPrice());
    }

	// GETTERS AND SETTERS OMITTED 

    @Override
    public String toString(){
        return "n∞ " + this.quantity + " " + this.it + ": " + this.price;
    }
}

The Item class is defined as follows:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class Item {
    private long id;
    private String name;
    private Categories cat;
    private double price;

    @JsonCreator
    public Item (@JsonProperty("id")long id, @JsonProperty("name")String n, @JsonProperty("cat")Categories c, @JsonProperty("price")double p){
        this.id = id;
        this.name = n;
        this.cat = c;
        this.price = p;
    }

	// GETTERS AND SETTERS OMITTED 

    @Override
    public String toString(){
        return this.name + "(" + this.cat + ")";
    }
}

We also had an enum for products categories.

public enum Categories {
    SPORT, BOOK, HARDWARE
}

At this point we had implemented a test class and created a Customer and an Order example instances and then we serialized them.

import java.util.ArrayList;
import java.util.Date;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

public class JacksonExample {
    public static void main(String[] args) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);

        Customer c1 = new Customer(1,"Davis", "Molinari");

        Item i1 = new Item(1, "Tablet XYZ", Categories.HARDWARE, 199.0);
        Item i2 = new Item(2, "Jackson Tutorial", Categories.BOOK, 19.00);
        Item i3 = new Item(3, "Running shoes", Categories.SPORT, 65.50);

        OrderItem oi1 = new OrderItem(2,i1);
        OrderItem oi2 = new OrderItem(3,i2);
        OrderItem oi3 = new OrderItem(1,i3);

        Order o = new Order(1000, c1, new ArrayList<OrderItem>(), new Date());
        o.addItemToOrder(oi1);
        o.addItemToOrder(oi2);
        o.addItemToOrder(oi3);

        String s = null;
        try {
            s = mapper.writeValueAsString(o);
        }
        catch (JsonProcessingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(s);
    }
}

The result, formatted with the indentation for an easy reading by the SerializationFeature.INDENT_OUTPUT option, was as follows:

{
  "id" : 1000,
  "cust" : {
    "id" : 1,
    "firstName" : "Davis",
    "lastName" : "Molinari"
  },
  "itemList" : [ {
    "quantity" : 2,
    "it" : {
      "id" : 1,
      "name" : "Tablet XYZ",
      "cat" : "HARDWARE",
      "price" : 199.0
    },
    "price" : 398.0
  }, {
    "quantity" : 3,
    "it" : {
      "id" : 2,
      "name" : "Jackson Tutorial",
      "cat" : "BOOK",
      "price" : 19.0
    },
    "price" : 57.0
  }, {
    "quantity" : 1,
    "it" : {
      "id" : 3,
      "name" : "Running shoes",
      "cat" : "SPORT",
      "price" : 65.5
    },
    "price" : 65.5
  } ],
  "placedDate" : "2015-09-23,16:42",
  "total" : 520.5
}

In this case they were not present in any of the objects created, properties with a null value. Now instead we modify our testing program, leaving some of our properties without any value; For example, when we create our Customer object we assign it only the firstName, leaving the value of the lastName null. Furthermore we “cut” the category value of one of the Item inserted in the Order (Tablet XYZ):

import java.util.ArrayList;
import java.util.Date;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

public class JacksonExample {
    public static void main(String[] args) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);

        // Customer c1 = new Customer(1,"Davis", "Molinari");
        Customer c1 = new Customer(1,"Davis", null);		// lastName is null

        // Item i1 = new Item(1, "Tablet XYZ", Categories.HARDWARE, 199.0);
        Item i1 = new Item(1, "Tablet XYZ", null, 199.0);	// category is null
        Item i2 = new Item(2, "Jackson Tutorial", Categories.BOOK, 19.00);
        Item i3 = new Item(3, "Running shoes", Categories.SPORT, 65.50);

        OrderItem oi1 = new OrderItem(2,i1);
        OrderItem oi2 = new OrderItem(3,i2);
        OrderItem oi3 = new OrderItem(1,i3);

        Order o = new Order(1000, c1, new ArrayList<OrderItem>(), new Date());
        o.addItemToOrder(oi1);
        o.addItemToOrder(oi2);
        o.addItemToOrder(oi3);

        String s = null;
        try {
            s = mapper.writeValueAsString(o);
        }
        catch (JsonProcessingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(s);
    }
}

The result we get this time from the serialization process is the following:

{
  "id" : 1000,
  "cust" : {
    "id" : 1,
    "firstName" : "Davis",
    "lastName" : null
  },
  "itemList" : [ {
    "quantity" : 2,
    "it" : {
      "id" : 1,
      "name" : "Tablet XYZ",
      "cat" : null,
      "price" : 199.0
    },
    "price" : 398.0
  }, {
    "quantity" : 3,
    "it" : {
      "id" : 2,
      "name" : "Jackson Tutorial",
      "cat" : "BOOK",
      "price" : 19.0
    },
    "price" : 57.0
  }, {
    "quantity" : 1,
    "it" : {
      "id" : 3,
      "name" : "Running shoes",
      "cat" : "SPORT",
      "price" : 65.5
    },
    "price" : 65.5
  } ],
  "placedDate" : "2015-09-24,12:53",
  "total" : 520.5
}

As we can see, this time the “lastName” property of the Customer and the “cat” property of the first Item added to the Order have a null value. Our goal here is to avoid that these properties, which have a null value, appear in the serialized JSON string.
Let’s see how to do it in the two ways listed at the beginning of article:

1) Exclusion at class level with the @JsonInclude(Include.NON_NULL) annotation
The first method consists in using the @JsonInclude annotation, telling it to exclude null values through the Include.NON_NULL option.
To check if it works correctly we modify the Customer class by adding the annotation at the beginning:

@JsonInclude(Include.NON_NULL)
public class Customer {

	// CLASS CONTENT OMITTED
}

By rerunning the test program, this time we get the following result:

{
  "id" : 1000,
  "cust" : {
    "id" : 1,
    "firstName" : "Davis"
  },
  "itemList" : [ {
    "quantity" : 2,
    "it" : {
      "id" : 1,
      "name" : "Tablet XYZ",
      "cat" : null,
      "price" : 199.0
    },
    "price" : 398.0
  }, {
    "quantity" : 3,
    "it" : {
      "id" : 2,
      "name" : "Jackson Tutorial",
      "cat" : "BOOK",
      "price" : 19.0
    },
    "price" : 57.0
  }, {
    "quantity" : 1,
    "it" : {
      "id" : 3,
      "name" : "Running shoes",
      "cat" : "SPORT",
      "price" : 65.5
    },
    "price" : 65.5
  } ],
  "placedDate" : "2015-09-24,15:42",
  "total" : 520.5
}

As we can see, this time the properties of the Customer object that were null have not been included in the JSON serialization string. It is not present, unlike before, the line:

"lastName" : null

In this way, however, we acted at class level and, having annotated with @JsonInclude only the Customer class, we excluded only its null value properties. We can indeed see that it is still present the line:

"cat" : null

This is because properties with null value in the Item class are not excluded from serialization.
To exclude also null properties of the Item class we must annotate also it with the @JsonInclude(Include.NON_NULL)

@JsonInclude(Include.NON_NULL)
public class Item {

	// CLASS CONTENT OMITTED

}

Running again the testing program we can see that now properties of the Item class with a null value don’t appear in the serialization string. In fact, the first Item of the list does not present the “cat” property:

{
  "id" : 1000,
  "cust" : {
    "id" : 1,
    "firstName" : "Davis"
  },
  "itemList" : [ {
    "quantity" : 2,
    "it" : {
      "id" : 1,
      "name" : "Tablet XYZ",
      "price" : 199.0
    },
    "price" : 398.0
  }, {
    "quantity" : 3,
    "it" : {
      "id" : 2,
      "name" : "Jackson Tutorial",
      "cat" : "BOOK",
      "price" : 19.0
    },
    "price" : 57.0
  }, {
    "quantity" : 1,
    "it" : {
      "id" : 3,
      "name" : "Running shoes",
      "cat" : "SPORT",
      "price" : 65.5
    },
    "price" : 65.5
  } ],
  "placedDate" : "2015-09-24,17:21",
  "total" : 520.5
}

2) Exclusion at global level through ObjectMapper configuration
Let’s look now at the second solution, which presupposes to set the null values exclusion strategy as common behavior for the serialization of all classes, setting an ObjectMapper property.
As first thing we restore our POJOs, removing the annotations we had previously introduced into our classes.

public class Customer {

	// CLASS CONTENT OMITTED
}
public class Item {

	// CLASS CONTENT OMITTED
}

At this point we have to change the test class, specifying to our ObjectMapper to consider, during serialization, only the properties that have a value other than null. To do that we must invoke on the ObjectMapper the setSerializationInclusion method, passing it JsonInclude.Include.NON_NULL as a parameter.

mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

So, we can change the test class JacksonExample

import java.util.ArrayList;
import java.util.Date;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

public class JacksonExample {
    public static void main(String[] args) {
        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // SETTING THE BEHAVIOR

        // Customer c1 = new Customer(1,"Davis", "Molinari");
        Customer c1 = new Customer(1,"Davis", null);

        // Item i1 = new Item(1, "Tablet XYZ", Categories.HARDWARE, 199.0);
        Item i1 = new Item(1, "Tablet XYZ", null, 199.0);
        Item i2 = new Item(2, "Jackson Tutorial", Categories.BOOK, 19.00);
        Item i3 = new Item(3, "Running shoes", Categories.SPORT, 65.50);

        OrderItem oi1 = new OrderItem(2,i1);
        OrderItem oi2 = new OrderItem(3,i2);
        OrderItem oi3 = new OrderItem(1,i3);

        Order o = new Order(1000, c1, new ArrayList<OrderItem>(), new Date());
        o.addItemToOrder(oi1);
        o.addItemToOrder(oi2);
        o.addItemToOrder(oi3);

        String s = null;
        try {
            s = mapper.writeValueAsString(o);
        }
        catch (JsonProcessingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println(s);
    }
}

and then we can execute it:

{
  "id" : 1000,
  "cust" : {
    "id" : 1,
    "firstName" : "Davis"
  },
  "itemList" : [ {
    "quantity" : 2,
    "it" : {
      "id" : 1,
      "name" : "Tablet XYZ",
      "price" : 199.0
    },
    "price" : 398.0
  }, {
    "quantity" : 3,
    "it" : {
      "id" : 2,
      "name" : "Jackson Tutorial",
      "cat" : "BOOK",
      "price" : 19.0
    },
    "price" : 57.0
  }, {
    "quantity" : 1,
    "it" : {
      "id" : 3,
      "name" : "Running shoes",
      "cat" : "SPORT",
      "price" : 65.5
    },
    "price" : 65.5
  } ],
  "placedDate" : "2015-09-24,17:40",
  "total" : 520.5
}

As we can see from the output, in the JSON string don’t appear the properties valued to null of Item and Customer classes. Acting directly on the ObjectMapper, as we said, we apply the behavior to all classes.

So in summary, this second method should be used when we are sure that the exclusion of null values need to be applied to all classes serialized with that ObjectMapper (assuming, of course, to have access to the ObjectMapper used). The first method, with the annotation at the single class level, allows us to define a different behavior for different classes as needed and for this reason it’s more flexible.

See also: Jackson:using @JsonIgnore and @JsonProperty annotations to exclude a property only from JSON deserialization

One thought on “Jackson: how to exclude null value properties from JSON serialization

  1. Pingback: Jackson JSON: difference between @JsonIgnore and @JsonIgnoreProperties annotations | Dede Blog

Leave a Reply

Your email address will not be published. Required fields are marked *