What Is Plain Old Java Object (POJO) ? – Full Stack QA (viralqa.com)
How To Create POJO Classes Of A JSON Object Payload – Full Stack QA (viralqa.com)
How To Create POJO Classes Of A JSON Array Payload – Full Stack QA (viralqa.com)
How To Create POJO Classes Of A Nested JSON Payload – Full Stack QA (viralqa.com)
Serialization – Java Object To JSON Object Using Jackson API – Full Stack QA (viralqa.com)
De-Serialization – JSON Object To Java Object Using Jackson API – Full Stack QA (viralqa.com)
@JsonInclude Annotation – Ignore Default Values In Payload – Full Stack QA (viralqa.com)
@JsonInclude Annotation – Ignore Default Values In Payload
@JsonInclude Annotation – Ignore Null & Empty Values In Payload
We will learn about an important annotation @JsonInclude of Jackson library which helps in eliminating default, null, empty, etc values. For this post, we will focus on excluding null and empty values in a payload.
Maven Dependency
Since we are going to use an annotation provided by Jackson API, we need to add the Jackson library in our project. I am using maven as a build tool here.
Always try to use the latest dependency from the Central Maven Repository. I will use the below dependency at the time of writing this post.
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.1</version>
</dependency>
We have already learned about @JsonInclude annotation and different values of Include enum in the previous post.
Usage of NON_NULL
In Java, a default value is provided to class variables. A chart is below:-
Data Type | Default Value (for fields) |
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d |
char | ‘u0000’ |
String (or any object) | null |
boolean | false |
The most dangerous default value is NULL. If we do not put checks or validation of null values we may end up with NullPointerException. Sometimes ignoring all default values is not good and we just want to ignore NULL values. In that case, we can use NON_NULL of Include enum as shown below:-
package JacksonTutorials;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Employee {
// private variables or data members of pojo class
private String firstName;
private String lastName;
private String gender;
private int age;
private double salary;
private boolean married;
// Getter and setter methods
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public boolean getMarried() {
return married;
}
public void setMarried(boolean married) {
this.married = married;
}
}
Converting Java Object to JSON Object
package JacksonTutorials;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class AnnotationJsonIncludeExample {
public static void main(String[] args) throws JsonProcessingException {
// Just create an object of Pojo class
Employee employee = new Employee();
// Set value as you wish
employee.setFirstName("Amod");
employee.setMarried(true);
// Converting a Java class object to a JSON payload as string
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(employee);
System.out.println(employeeJson);
}
}
Output
{
"firstName" : "Amod",
"age" : 0,
"salary" : 0.0,
"married" : true
}
We have not set values for fields ‘lastName’, ‘gender’,’age’ and ‘salary’. Since default values of fields ‘lastName’ and ‘gender’ are NULL which will be ignored during serialization but default values of fields ‘age’ and ‘salary’ are zero, so they will be included in JSON which you can see in above output.
Usage of NON_EMPTY
The scope of NON_EMPTY is greater than NON_NULL or we can say NON_EMPTY includes NON_NULL in it with others. As per the official document:-
NON_EMPTY + NON_NULL + NON_ABSENT
Properties of a class will not be included during serialization if the value of the property is NULL or Empty. NULL we have already seen. It is for String and reference variables. EMPTY is a little tricky. The definition of emptiness depends on the data type of field.
- A NULL value is also considered as EMPTY.
- For Collections and Map, isEmpty() method is called to check emptiness i.e. without any content.
- For an array and string, the length is checked for emptiness.
A POJO with different data types:-
package JacksonTutorials;
import java.util.List;
import java.util.Map;
public class SeniorEmployee {
// private variables or data members of pojo class
private String firstName;
private String lastName;
private String gender;
private int age;
private double salary;
private boolean married;
private String[] mobileNo;
private List<String> cars;
private Map<String,String> familyMembers;
// Getter and setter methods
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public boolean getMarried() {
return married;
}
public void setMarried(boolean married) {
this.married = married;
}
public String[] getMobileNo() {
return mobileNo;
}
public void setMobileNo(String[] mobileNo) {
this.mobileNo = mobileNo;
}
public List<String> getCars() {
return cars;
}
public void setCars(List<String> cars) {
this.cars = cars;
}
public Map<String, String> getFamilyMembers() {
return familyMembers;
}
public void setFamilyMembers(Map<String, String> familyMembers) {
this.familyMembers = familyMembers;
}
}
Let’s not set any value for any field of POJO class:-
@Test(priority = 1)
public void noValuessetForAnyField() throws JsonProcessingException {
// Just create an object of Pojo class
SeniorEmployee seniorEmployee = new SeniorEmployee();
// Converting a Java class object to a JSON payload as string
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(seniorEmployee);
System.out.println("Output of noValuessetForAnyField...");
System.out.println(employeeJson);
}
Output:-
Output of noValuessetForAnyField...
{
"firstName" : null,
"lastName" : null,
"gender" : null,
"age" : 0,
"salary" : 0.0,
"married" : false,
"mobileNo" : null,
"cars" : null,
"familyMembers" : null
}
We can see how fields are initialized with default values. Obviously we can ignore NULL values using NON_NULL which we have seen above. Let’s add NON_NULL at the class level and create an object as below:-
@Test(priority = 1)
public void noValueSetForArrayListMap() throws JsonProcessingException {
// Just create an object of Pojo class
SeniorEmployee seniorEmployee = new SeniorEmployee();
seniorEmployee.setFirstName("Amod");
seniorEmployee.setLastName("Mahajan");
seniorEmployee.setAge(29);
seniorEmployee.setGender("Male");
seniorEmployee.setSalary(12323.56);
seniorEmployee.setMarried(false);
// Empty array
String[] mobileNo = {};
seniorEmployee.setMobileNo(mobileNo);
// Empty list
List<String> cars = new ArrayList<String>();
seniorEmployee.setCars(cars);
// Empty Map
Map<String,String> familyMembers = new HashMap<>();
seniorEmployee.setFamilyMembers(familyMembers);
// Converting a Java class object to a JSON payload as string
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(seniorEmployee);
System.out.println("Output of noValueSetForArrayListMap...");
System.out.println(employeeJson);
}
Output
Output of valueSetForFields...
{
"firstName" : "Amod",
"lastName" : "Mahajan",
"gender" : "Male",
"age" : 29,
"salary" : 12323.56,
"married" : false,
"mobileNo" : [ ],
"cars" : [ ],
"familyMembers" : { }
}
You can see NON_NULL did not serve the purpose here as we have empty values not null.
Let’s add NON_EMPTY at class level as:-
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class SeniorEmployee
Let’s rerun both @Test methods again:-
Output of first @Test Method:-
{
"age" : 0,
"salary" : 0.0,
"married" : false
}
Output of second @Test Method:-
{
"firstName" : "Amod",
"lastName" : "Mahajan",
"gender" : "Male",
"age" : 29,
"salary" : 12323.56,
"married" : false
}
I have use annotation at class level but you can use it at property level as well.
Complete Program
package JacksonTutorials;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class NonEmptyExample {
@Test(priority = 1)
public void noValuesSetForAnyField() throws JsonProcessingException {
// Just create an object of Pojo class
SeniorEmployee seniorEmployee = new SeniorEmployee();
// Converting a Java class object to a JSON payload as string
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(seniorEmployee);
System.out.println("Output of noValuesSetForAnyField...");
System.out.println(employeeJson);
}
@Test(priority = 2)
public void valueSetForFields() throws JsonProcessingException {
// Just create an object of Pojo class
SeniorEmployee seniorEmployee = new SeniorEmployee();
seniorEmployee.setFirstName("Amod");
seniorEmployee.setLastName("Mahajan");
seniorEmployee.setAge(29);
seniorEmployee.setGender("Male");
seniorEmployee.setSalary(12323.56);
seniorEmployee.setMarried(false);
String[] mobileNo = new String[2];
mobileNo[0] = "12345";
mobileNo[1] = "67890";
seniorEmployee.setMobileNo(mobileNo);
List<String> cars = new ArrayList<String>();
cars.add("Audi");
cars.add("bmw");
seniorEmployee.setCars(cars);
Map<String,String> familyMembers = new HashMap<>();
familyMembers.put("1", "Father");
familyMembers.put("2", "Mother");
familyMembers.put("3", "Brother");
seniorEmployee.setFamilyMembers(familyMembers);
// Converting a Java class object to a JSON payload as string
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(seniorEmployee);
System.out.println("Output of valueSetForFields...");
System.out.println(employeeJson);
}
@Test(priority = 3)
public void noValueSetForArrayListMap() throws JsonProcessingException {
// Just create an object of Pojo class
SeniorEmployee seniorEmployee = new SeniorEmployee();
seniorEmployee.setFirstName("Amod");
seniorEmployee.setLastName("Mahajan");
seniorEmployee.setAge(29);
seniorEmployee.setGender("Male");
seniorEmployee.setSalary(12323.56);
seniorEmployee.setMarried(false);
// Empty array
String[] mobileNo = {};
seniorEmployee.setMobileNo(mobileNo);
// Empty list
List<String> cars = new ArrayList<String>();
seniorEmployee.setCars(cars);
// Empty Map
Map<String,String> familyMembers = new HashMap<>();
seniorEmployee.setFamilyMembers(familyMembers);
// Converting a Java class object to a JSON payload as string
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(seniorEmployee);
System.out.println("Output of noValueSetForArrayListMap...");
System.out.println(employeeJson);
}
}
Output (When NON_EMPTY added at class level)
Output of noValuesSetForAnyField...
{
"age" : 0,
"salary" : 0.0,
"married" : false
}
Output of valueSetForFields...
{
"firstName" : "Amod",
"lastName" : "Mahajan",
"gender" : "Male",
"age" : 29,
"salary" : 12323.56,
"married" : false,
"mobileNo" : [ "12345", "67890" ],
"cars" : [ "Audi", "bmw" ],
"familyMembers" : {
"1" : "Father",
"2" : "Mother",
"3" : "Brother"
}
}
Output of noValueSetForArrayListMap...
{
"firstName" : "Amod",
"lastName" : "Mahajan",
"gender" : "Male",
"age" : 29,
"salary" : 12323.56,
"married" : false
}
How Getter & Setter Methods Matter For Serialization Deserialization Using POJO
A typical POJO class consists of private fields with its getter and setter methods or accessors (Which is not mandatory). You can have fields and accessors with any access level in a POJO. In this post, we will see how presence and absence of getter or setter methods impacts serialization and deserialization process.
POJO with proper getter and setter methods
Below is a POJO class with some private properties and their public accessors i.e. getter and setter methods. I have put a print statement in all getter and setter methods just to show method calling during serialization and deserialization process.
ackage GameOfGetterSetterPOJO;
import com.fasterxml.jackson.annotation.JsonInclude;
public class EmployeePojoWithGetterSetterMethods {
// private variables or data members of pojo class
private String firstName;
private String lastName;
private String gender;
private int age;
private double salary;
private boolean married;
// Getter and setter methods
public String getFirstName() {
System.out.println("Getter - First Name : "+ firstName);
return firstName;
}
public void setFirstName(String firstName) {
System.out.println("Setter - First Name : "+ firstName);
this.firstName = firstName;
}
public String getLastName() {
System.out.println("Getter - Last Name : "+ lastName);
return lastName;
}
public void setLastName(String lastName) {
System.out.println("Setter - Last Name : "+ lastName);
this.lastName = lastName;
}
public String getGender() {
System.out.println("Getter - Gender : "+ gender);
return gender;
}
public void setGender(String gender) {
System.out.println("Setter - Gender : "+ gender);
this.gender = gender;
}
public int getAge() {
System.out.println("Getter - Age : "+ age);
return age;
}
public void setAge(int age) {
System.out.println("Setter - Age : "+ age);
this.age = age;
}
public double getSalary() {
System.out.println("Getter - Salary : "+ salary);
return salary;
}
public void setSalary(double salary) {
System.out.println("Setter - Salary : "+ salary);
this.salary = salary;
}
public boolean getMarried() {
System.out.println("Getter - Married : "+ married);
return married;
}
public void setMarried(boolean married) {
System.out.println("Setter - Married : "+ married);
this.married = married;
}
}
Let’s serialize with the above POJO class.
@Test
public void serializeWithBothGetterSetter() throws JsonProcessingException {
// Just create an object of Pojo class
EmployeePojoWithGetterSetterMethods employeePojoWithGetterSetterMethods = new EmployeePojoWithGetterSetterMethods();
employeePojoWithGetterSetterMethods.setFirstName("Amod");
employeePojoWithGetterSetterMethods.setLastName("Mahajan");
employeePojoWithGetterSetterMethods.setAge(29);
employeePojoWithGetterSetterMethods.setGender("Male");
employeePojoWithGetterSetterMethods.setSalary(12323.56);
employeePojoWithGetterSetterMethods.setMarried(false);
// Converting a Java class object to a JSON payload as string
System.out.println("Serialization...");
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(employeePojoWithGetterSetterMethods);
System.out.println(employeeJson);
}
Output
Setter - First Name : Amod
Setter - Last Name : Mahajan
Setter - Age : 29
Setter - Gender : Male
Setter - Salary : 12323.56
Setter - Married : false
Serialization...
Getter - First Name : Amod
Getter - Last Name : Mahajan
Getter - Gender : Male
Getter - Age : 29
Getter - Salary : 12323.56
Getter - Married : false
{
"firstName" : "Amod",
"lastName" : "Mahajan",
"gender" : "Male",
"age" : 29,
"salary" : 12323.56,
"married" : false
}
Since we are calling setter methods to set values of properties so setter methods are executed. While serializing, getter methods are called internally as you can see in the output. That seems to be very obvious as to form a JSON they need a value of fields that can be retrieved using getter methods.
Let’s deserialize with the above POJO class.
@Test
public void deserializeWithBothGetterSetter() throws JsonProcessingException {
String jsonString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\",\r\n" +
" \"age\" : 29,\r\n" +
" \"salary\" : 12323.56,\r\n" +
" \"married\" : false\r\n" +
"}";
System.out.println("Deserialization...");
ObjectMapper objectMapper = new ObjectMapper();
EmployeePojoWithGetterSetterMethods employeePojoWithGetterSetterMethods = objectMapper.readValue(jsonString, EmployeePojoWithGetterSetterMethods.class);
System.out.println("First name :- "+employeePojoWithGetterSetterMethods.getFirstName());
System.out.println("Last name :- "+employeePojoWithGetterSetterMethods.getLastName());
System.out.println("Age :- "+employeePojoWithGetterSetterMethods.getAge());
System.out.println("Gender :- "+employeePojoWithGetterSetterMethods.getGender());
System.out.println("Salary :- "+employeePojoWithGetterSetterMethods.getSalary());
System.out.println("Married :- "+employeePojoWithGetterSetterMethods.getMarried());
}
Output
Deserialization...
Setter - First Name : Amod
Setter - Last Name : Mahajan
Setter - Gender : Male
Setter - Age : 29
Setter - Salary : 12323.56
Setter - Married : false
Getter - First Name : Amod
First name :- Amod
Getter - Last Name : Mahajan
Last name :- Mahajan
Getter - Age : 29
Age :- 29
Getter - Gender : Male
Gender :- Male
Getter - Salary : 12323.56
Salary :- 12323.56
Getter - Married : false
Married :- false
During deserialization, setter methods are called internally as you can see in output as well. To print values, we are explicitly calling getter methods which you can see in the output.
In short:-
Serialization uses Getter methods.
Deserialization uses Setter methods
Let’s see what happens when setter methods are absent.
POJO with only getter methods
I removed all the traditional setter methods of properties. We have just setter methods now in the POJO class.
package GameOfGetterSetterPOJO;
public class EmployeeWithGetterMethodsOnly {
// private variables or data members of pojo class
private String firstName;
private String lastName;
private String gender;
private int age;
private double salary;
private boolean married;
// Getter methods only
public String getFirstName() {
System.out.println("Getter - First Name : "+ firstName);
return firstName;
}
public String getLastName() {
System.out.println("Getter - Last Name : "+ lastName);
return lastName;
}
public String getGender() {
System.out.println("Getter - Gender : "+ gender);
return gender;
}
public int getAge() {
System.out.println("Getter - Age : "+ age);
return age;
}
public double getSalary() {
System.out.println("Getter - Salary : "+ salary);
return salary;
}
public boolean getMarried() {
System.out.println("Getter - Married : "+ married);
return married;
}
}
Let’s serialize with the above POJO class.
@Test
public void serializeWithGetterOnly() throws JsonProcessingException {
// Just create an object of Pojo class but we can set values as we do not have any setter methods
EmployeeWithGetterMethodsOnly employeeWithGetterMethodsOnly = new EmployeeWithGetterMethodsOnly();
// Converting a Java class object to a JSON payload as string
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(employeeWithGetterMethodsOnly);
System.out.println("Serialization...");
System.out.println(employeeJson);
}
Output
Getter - First Name : null
Getter - Last Name : null
Getter - Gender : null
Getter - Age : 0
Getter - Salary : 0.0
Getter - Married : false
Serialization...
{
"firstName" : null,
"lastName" : null,
"gender" : null,
"age" : 0,
"salary" : 0.0,
"married" : false
}
Since we do not have setter methods we can not serialize with expected values. All class level fields are initialized with default values which will be used for serialization as you can see in output.
Let’s deserialize with the above POJO class.
@Test
public void deserializeWithGetterOnly() throws JsonProcessingException {
String jsonString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\",\r\n" +
" \"age\" : 29,\r\n" +
" \"salary\" : 12323.56,\r\n" +
" \"married\" : false\r\n" +
"}";
System.out.println("Deserialization...");
ObjectMapper objectMapper = new ObjectMapper();
EmployeeWithGetterMethodsOnly employeeWithGetterMethodsOnly = objectMapper.readValue(jsonString, EmployeeWithGetterMethodsOnly.class);
System.out.println("First name :- "+employeeWithGetterMethodsOnly.getFirstName());
System.out.println("Last name :- "+employeeWithGetterMethodsOnly.getLastName());
System.out.println("Age :- "+employeeWithGetterMethodsOnly.getAge());
System.out.println("Gender :- "+employeeWithGetterMethodsOnly.getGender());
System.out.println("Salary :- "+employeeWithGetterMethodsOnly.getSalary());
System.out.println("Married :- "+employeeWithGetterMethodsOnly.getMarried());
}
Output
Deserialization...
Getter - First Name : Amod
First name :- Amod
Getter - Last Name : Mahajan
Last name :- Mahajan
Getter - Age : 29
Age :- 29
Getter - Gender : Male
Gender :- Male
Getter - Salary : 12323.56
Salary :- 12323.56
Getter - Married : false
Married :- false
Deserialization had no issues with the absence of setter methods. If you see deserialization example of POJO with both getter and setter methods, in output setter methods were called internally which is not the same case in this example.
In short:-
If you have only getter methods in POJO class, you can not serialize it with desired values but can deserialize it easily. You do not need dedicated setter methods for deserializing.
Let’s see what happens when the getter methods are absent.
POJO with only setter methods
package GameOfGetterSetterPOJO;
public class EmployeePojoWithSetterMethodsOnly {
// private variables or data members of pojo class
private String firstName;
private String lastName;
private String gender;
private int age;
private double salary;
private boolean married;
// Setter methods
public void setFirstName(String firstName) {
System.out.println("Setter - First Name : "+ firstName);
this.firstName = firstName;
}
public void setLastName(String lastName) {
System.out.println("Setter - Last Name : "+ lastName);
this.lastName = lastName;
}
public void setGender(String gender) {
System.out.println("Setter - Gender : "+ gender);
this.gender = gender;
}
public void setAge(int age) {
System.out.println("Setter - Age : "+ age);
this.age = age;
}
public void setSalary(double salary) {
System.out.println("Setter - Salary : "+ salary);
this.salary = salary;
}
public void setMarried(boolean married) {
System.out.println("Setter - Married : "+ married);
this.married = married;
}
}
Let’s serialize with the above POJO class.
@Test
public void serializeWithSetterOnly() throws JsonProcessingException {
// Just create an object of Pojo class
EmployeePojoWithSetterMethodsOnly employeePojoWithSetterMethodsOnly = new EmployeePojoWithSetterMethodsOnly();
employeePojoWithSetterMethodsOnly.setFirstName("Amod");
employeePojoWithSetterMethodsOnly.setLastName("Mahajan");
employeePojoWithSetterMethodsOnly.setAge(29);
employeePojoWithSetterMethodsOnly.setGender("Male");
employeePojoWithSetterMethodsOnly.setSalary(12323.56);
employeePojoWithSetterMethodsOnly.setMarried(false);
// Converting a Java class object to a JSON payload as string
System.out.println("Serialization...");
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(employeePojoWithSetterMethodsOnly);
System.out.println(employeeJson);
}
Output
Setter - First Name : Amod
Setter - Last Name : Mahajan
Setter - Age : 29
Setter - Gender : Male
Setter - Salary : 12323.56
Setter - Married : false
Serialization...
FAILED: serializeWithSetterOnly
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class GameOfGetterSetterPOJO.EmployeePojoWithSetterMethodsOnly and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1277)
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400)
We got an exception stating “No serializer found” which is obvious as serialization looks for getter methods that are not available.
Let’s deserialize with the above POJO class.
@Test
public void deserializeWithSetterOnly() throws JsonProcessingException {
String jsonString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\",\r\n" +
" \"age\" : 29,\r\n" +
" \"salary\" : 12323.56,\r\n" +
" \"married\" : false\r\n" +
"}";
System.out.println("Deserialization...");
ObjectMapper objectMapper = new ObjectMapper();
EmployeePojoWithSetterMethodsOnly employeePojoWithSetterMethodsOnly = objectMapper.readValue(jsonString, EmployeePojoWithSetterMethodsOnly.class);
}
Output
Deserialization...
Setter - First Name : Amod
Setter - Last Name : Mahajan
Setter - Gender : Male
Setter - Age : 29
Setter - Salary : 12323.56
Since we do not have getter methods so we can not retrieve values of properties but that does not mean that properties are not initialized. Let’s add other getter methods to fetch values.
package GameOfGetterSetterPOJO;
public class EmployeePojoWithSetterMethodsOnly {
// private variables or data members of pojo class
private String firstName;
private String lastName;
private String gender;
private int age;
private double salary;
private boolean married;
// Setter methods
public void setFirstName(String firstName) {
System.out.println("Setter - First Name : "+ firstName);
this.firstName = firstName;
}
public void setLastName(String lastName) {
System.out.println("Setter - Last Name : "+ lastName);
this.lastName = lastName;
}
public void setGender(String gender) {
System.out.println("Setter - Gender : "+ gender);
this.gender = gender;
}
public void setAge(int age) {
System.out.println("Setter - Age : "+ age);
this.age = age;
}
public void setSalary(double salary) {
System.out.println("Setter - Salary : "+ salary);
this.salary = salary;
}
public void setMarried(boolean married) {
System.out.println("Setter - Married : "+ married);
this.married = married;
}
public String alternateGetFirstName() {
return firstName;
}
public String alternateGetLastName() {
return lastName;
}
public String alternateGetGender() {
return gender;
}
public int alternateGetAge() {
return age;
}
public double alternateGetSalary() {
return salary;
}
public boolean alternateGetMarried() {
return married;
}
}
Updated deserialize code
@Test
public void deserializeWithSetterOnly() throws JsonProcessingException {
String jsonString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\",\r\n" +
" \"age\" : 29,\r\n" +
" \"salary\" : 12323.56,\r\n" +
" \"married\" : false\r\n" +
"}";
System.out.println("Deserialization...");
ObjectMapper objectMapper = new ObjectMapper();
EmployeePojoWithSetterMethodsOnly employeePojoWithSetterMethodsOnly = objectMapper.readValue(jsonString, EmployeePojoWithSetterMethodsOnly.class);
System.out.println("First name :- "+employeePojoWithSetterMethodsOnly.alternateGetFirstName());
System.out.println("Last name :- "+employeePojoWithSetterMethodsOnly.alternateGetLastName());
System.out.println("Age :- "+employeePojoWithSetterMethodsOnly.alternateGetAge());
System.out.println("Gender :- "+employeePojoWithSetterMethodsOnly.alternateGetGender());
System.out.println("Salary :- "+employeePojoWithSetterMethodsOnly.alternateGetSalary());
System.out.println("Married :- "+employeePojoWithSetterMethodsOnly.alternateGetMarried());
}
Output
Deserialization...
Setter - First Name : Amod
Setter - Last Name : Mahajan
Setter - Gender : Male
Setter - Age : 29
Setter - Salary : 12323.56
Setter - Married : false
First name :- Amod
Last name :- Mahajan
Age :- 29
Gender :- Male
Salary :- 12323.56
Married :- false
So if you have only setter methods then you can not serialize even you have any alternative getter methods. With setter methods, you can deserialize it easily but you need alternate getter methods to retrieve values.
If you do not have traditional or intended getter methods then you can not serialize and deserialize it.
@JsonIgnore Annotation Of Jackson API – Exclude Field Of Pojo From Serialization And Deserialization
There may be multiple fields in a Pojo class and sometimes you don’t require some fields to participate in serialization and deserialization process. I can give a scenario where we may need to ignore some properties of a Pojo class.
In a POJO class, we may have properties whose values are derived from other properties. For example- If we know the first name and last name of a person we can get their full name. If we know the age of a person we can get to know if a person is eligible for the vote or not. We can have getter methods for such properties to return values but should not be allowed to set from outside.
Consider below POJO.
package JacksonTutorials;
public class EmployeePojoWithoutJsonIgnore {
// private variables or data members of pojo class
private String firstName;
private String lastName;
private String gender;
private int age;
private double salary;
private boolean married;
private String fullName;
private boolean eligibleForVote;
// Getter and setter methods
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public boolean getMarried() {
return married;
}
public void setMarried(boolean married) {
this.married = married;
}
public String getFullName() {
return this.fullName;
}
public boolean getEligibleForVote() {
return this.eligibleForVote;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public void setEligibleForVote(boolean eligibleForVote) {
this.eligibleForVote = eligibleForVote;
}
}
Serialization using above POJO
@Test
public void serializationWithoutJsonIgnore() throws JsonProcessingException {
// Just create an object of Pojo class
EmployeePojoWithoutJsonIgnore employeePojoWithoutJsonIgnore = new EmployeePojoWithoutJsonIgnore();
employeePojoWithoutJsonIgnore.setFirstName("Amod");
employeePojoWithoutJsonIgnore.setLastName("Mahajan");
employeePojoWithoutJsonIgnore.setAge(29);
employeePojoWithoutJsonIgnore.setGender("Male");
employeePojoWithoutJsonIgnore.setSalary(12323.56);
employeePojoWithoutJsonIgnore.setMarried(false);
employeePojoWithoutJsonIgnore.setFullName("Animesh Prashant");
employeePojoWithoutJsonIgnore.setEligibleForVote(false);
// Converting a Java class object to a JSON payload as string
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(employeePojoWithoutJsonIgnore);
System.out.println("Serialization...");
System.out.println(employeeJson);
}
Output
Serialization...
{
"firstName" : "Amod",
"lastName" : "Mahajan",
"gender" : "Male",
"age" : 29,
"salary" : 12323.56,
"married" : false,
"fullName" : "Animesh Prashant",
"eligibleForVote" : false
}
Deserialization using above POJO
@Test
public void deserializationWithoutJsonIgnore() throws JsonMappingException, JsonProcessingException
{
String employeeString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\",\r\n" +
" \"age\" : 29,\r\n" +
" \"salary\" : 12323.56,\r\n" +
" \"married\" : false,\r\n" +
" \"fullName\" : \"Amod Mahajan Gupta\",\r\n" +
" \"eligibleForVote\" : false\r\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
EmployeePojoWithoutJsonIgnore employeePojoWithoutJsonIgnore2 = objectMapper.readValue(employeeString, EmployeePojoWithoutJsonIgnore.class);
System.out.println("Deserialization...");
System.out.println("First name :- "+employeePojoWithoutJsonIgnore2.getFirstName());
System.out.println("Last name :- "+employeePojoWithoutJsonIgnore2.getLastName());
System.out.println("Age :- "+employeePojoWithoutJsonIgnore2.getAge());
System.out.println("Gender :- "+employeePojoWithoutJsonIgnore2.getGender());
System.out.println("Salary :- "+employeePojoWithoutJsonIgnore2.getSalary());
System.out.println("Married :- "+employeePojoWithoutJsonIgnore2.getMarried());
System.out.println("Eligible for vote :- "+employeePojoWithoutJsonIgnore2.getEligibleForVote());
System.out.println("Full name :- "+employeePojoWithoutJsonIgnore2.getFullName());
}
Output:-
Deserialization...
First name :- Amod
Last name :- Mahajan
Age :- 29
Gender :- Male
Salary :- 12323.56
Married :- false
Eligible for vote :- false
Full name :- Amod Mahajan Gupta
If you observe output carefully we see values of properties of “fullName” and “eligibleForVote” have set wrongly. We should have calculated it. Ideally, these fields should have getter methods only just for quick access.
Complete Program
package JacksonTutorials;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SerialiDeserialExampleWithoutJsonIgnore {
@Test
public void serializationWithoutJsonIgnore() throws JsonProcessingException {
// Just create an object of Pojo class
EmployeePojoWithoutJsonIgnore employeePojoWithoutJsonIgnore = new EmployeePojoWithoutJsonIgnore();
employeePojoWithoutJsonIgnore.setFirstName("Amod");
employeePojoWithoutJsonIgnore.setLastName("Mahajan");
employeePojoWithoutJsonIgnore.setAge(29);
employeePojoWithoutJsonIgnore.setGender("Male");
employeePojoWithoutJsonIgnore.setSalary(12323.56);
employeePojoWithoutJsonIgnore.setMarried(false);
employeePojoWithoutJsonIgnore.setFullName("Animesh Prashant");
employeePojoWithoutJsonIgnore.setEligibleForVote(false);
// Converting a Java class object to a JSON payload as string
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(employeePojoWithoutJsonIgnore);
System.out.println("Serialization...");
System.out.println(employeeJson);
}
@Test
public void deserializationWithoutJsonIgnore() throws JsonMappingException, JsonProcessingException
{
String employeeString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\",\r\n" +
" \"age\" : 29,\r\n" +
" \"salary\" : 12323.56,\r\n" +
" \"married\" : false,\r\n" +
" \"fullName\" : \"Amod Mahajan Gupta\",\r\n" +
" \"eligibleForVote\" : false\r\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
EmployeePojoWithoutJsonIgnore employeePojoWithoutJsonIgnore2 = objectMapper.readValue(employeeString, EmployeePojoWithoutJsonIgnore.class);
System.out.println("Deserialization...");
System.out.println("First name :- "+employeePojoWithoutJsonIgnore2.getFirstName());
System.out.println("Last name :- "+employeePojoWithoutJsonIgnore2.getLastName());
System.out.println("Age :- "+employeePojoWithoutJsonIgnore2.getAge());
System.out.println("Gender :- "+employeePojoWithoutJsonIgnore2.getGender());
System.out.println("Salary :- "+employeePojoWithoutJsonIgnore2.getSalary());
System.out.println("Married :- "+employeePojoWithoutJsonIgnore2.getMarried());
System.out.println("Eligible for vote :- "+employeePojoWithoutJsonIgnore2.getEligibleForVote());
System.out.println("Full name :- "+employeePojoWithoutJsonIgnore2.getFullName());
}
}
There may be some fields that may be optional or just want to ignore it for time being.
If we want to ignore any property of a POJO class from serialization and deserialization we can use @JsonIgnore provided by Jackson API on that property. It is a marker annotation that indicates that the logical property that the accessor is to be ignored by introspection-based serialization and deserialization functionality. Annotation only needs to be added to one of the accessors (often getter method, but may be setter, field or creator parameter), if the complete removal of the property is desired.
POJO with JsonIgnore
package JacksonTutorials;
import com.fasterxml.jackson.annotation.JsonIgnore;
public class EmployeePojoWithJsonIgnore {
// private variables or data members of pojo class
private String firstName;
private String lastName;
private String gender;
private int age;
private double salary;
private boolean married;
@JsonIgnore
private String fullName;
@JsonIgnore
private boolean eligibleForVote;
// Getter and setter methods
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public boolean getMarried() {
return married;
}
public void setMarried(boolean married) {
this.married = married;
}
public String getFullName() {
return this.fullName;
}
public boolean getEligibleForVote() {
return this.eligibleForVote;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public void setEligibleForVote(boolean eligibleForVote) {
this.eligibleForVote = eligibleForVote;
}
}
Serialization using POJO with JsonIgnore
@Test
public void serializationWithJsonIgnore() throws JsonProcessingException {
// Just create an object of Pojo class
EmployeePojoWithJsonIgnore employeePojoWithJsonIgnore = new EmployeePojoWithJsonIgnore();
employeePojoWithJsonIgnore.setFirstName("Amod");
employeePojoWithJsonIgnore.setLastName("Mahajan");
employeePojoWithJsonIgnore.setAge(29);
employeePojoWithJsonIgnore.setGender("Male");
employeePojoWithJsonIgnore.setSalary(12323.56);
employeePojoWithJsonIgnore.setMarried(false);
employeePojoWithJsonIgnore.setFullName("Animesh Prashant");
employeePojoWithJsonIgnore.setEligibleForVote(false);
// Converting a Java class object to a JSON payload as string
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(employeePojoWithJsonIgnore);
System.out.println("Serialization...");
System.out.println(employeeJson);
}
Output:-
Serialization...
{
"firstName" : "Amod",
"lastName" : "Mahajan",
"gender" : "Male",
"age" : 29,
"salary" : 12323.56,
"married" : false
}
Deserialization using POJO with JsonIgnore
@Test
public void deserializationWithJsonIgnore() throws JsonMappingException, JsonProcessingException
{
String employeeString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\",\r\n" +
" \"age\" : 29,\r\n" +
" \"salary\" : 12323.56,\r\n" +
" \"married\" : false,\r\n" +
" \"fullName\" : \"Amod Mahajan Gupta\",\r\n" +
" \"eligibleForVote\" : false\r\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
EmployeePojoWithJsonIgnore employeePojoWithJsonIgnore2 = objectMapper.readValue(employeeString, EmployeePojoWithJsonIgnore.class);
System.out.println("Deserialization...");
System.out.println("First name :- "+employeePojoWithJsonIgnore2.getFirstName());
System.out.println("Last name :- "+employeePojoWithJsonIgnore2.getLastName());
System.out.println("Age :- "+employeePojoWithJsonIgnore2.getAge());
System.out.println("Gender :- "+employeePojoWithJsonIgnore2.getGender());
System.out.println("Salary :- "+employeePojoWithJsonIgnore2.getSalary());
System.out.println("Married :- "+employeePojoWithJsonIgnore2.getMarried());
System.out.println("Eligible for vote :- "+employeePojoWithJsonIgnore2.getEligibleForVote());
System.out.println("Full name :- "+employeePojoWithJsonIgnore2.getFullName());
}
Output
Deserialization...
First name :- Amod
Last name :- Mahajan
Age :- 29
Gender :- Male
Salary :- 12323.56
Married :- false
Eligible for vote :- false
Full name :- null
Complete program
package JacksonTutorials;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SerialiDeserialExampleWithJsonIgnore {
@Test
public void serializationWithJsonIgnore() throws JsonProcessingException {
// Just create an object of Pojo class
EmployeePojoWithJsonIgnore employeePojoWithJsonIgnore = new EmployeePojoWithJsonIgnore();
employeePojoWithJsonIgnore.setFirstName("Amod");
employeePojoWithJsonIgnore.setLastName("Mahajan");
employeePojoWithJsonIgnore.setAge(29);
employeePojoWithJsonIgnore.setGender("Male");
employeePojoWithJsonIgnore.setSalary(12323.56);
employeePojoWithJsonIgnore.setMarried(false);
employeePojoWithJsonIgnore.setFullName("Animesh Prashant");
employeePojoWithJsonIgnore.setEligibleForVote(false);
// Converting a Java class object to a JSON payload as string
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(employeePojoWithJsonIgnore);
System.out.println("Serialization...");
System.out.println(employeeJson);
}
@Test
public void deserializationWithJsonIgnore() throws JsonMappingException, JsonProcessingException
{
String employeeString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\",\r\n" +
" \"age\" : 29,\r\n" +
" \"salary\" : 12323.56,\r\n" +
" \"married\" : false,\r\n" +
" \"fullName\" : \"Amod Mahajan Gupta\",\r\n" +
" \"eligibleForVote\" : false\r\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
EmployeePojoWithJsonIgnore employeePojoWithJsonIgnore2 = objectMapper.readValue(employeeString, EmployeePojoWithJsonIgnore.class);
System.out.println("Deserialization...");
System.out.println("First name :- "+employeePojoWithJsonIgnore2.getFirstName());
System.out.println("Last name :- "+employeePojoWithJsonIgnore2.getLastName());
System.out.println("Age :- "+employeePojoWithJsonIgnore2.getAge());
System.out.println("Gender :- "+employeePojoWithJsonIgnore2.getGender());
System.out.println("Salary :- "+employeePojoWithJsonIgnore2.getSalary());
System.out.println("Married :- "+employeePojoWithJsonIgnore2.getMarried());
System.out.println("Eligible for vote :- "+employeePojoWithJsonIgnore2.getEligibleForVote());
System.out.println("Full name :- "+employeePojoWithJsonIgnore2.getFullName());
}
}
We have values for fields fullName and eligibleForVote in Json but it has not been deserialized as you can see it has default values not from Json.
@JsonIgnore annotation can be used with getter and setter methods as well.
@JsonIgnoreProperties Annotation Of Jackson API – Exclude Field Of Pojo From Serialization Or Deserialization Or Both
Limitations of @JsonIgnore
We use @JsonIgnore annotation at an individual property level or its one of the accessors to prevent a particular field from serialization and deserialization process. It has some limitations as below:-
- To ignore multiple properties, you need to mark each property with @JsonIgnore that makes it difficult to manage.
- If you mention @JsonIgnore at any one place i.e. at a property or its one of accessors then that property will be ignored from serialization and deserialization. We can not control if we want a property to be ignored by serialization or deserialization only.
These limitations can be overcome using @JsonIgnoreProperties.
Introduction of @JsonIgnoreProperties
It is an annotation that can be used to either suppress serialization of properties (during serialization) or ignore the processing of JSON properties read (during deserialization). This can be used at class level as well as at an individual property level like @JsonIgnore.
Let’s see some basic examples to understand it more clear.
Examples
Ignore multiple properties from serialization and deserialization
If we want to ignore multiple properties from both serialization and deserialization process then we can mention all property names at class level as below:-
@JsonIgnoreProperties({"gender","fullName"})
public class EmployeePojoWithJsonIgnoreProperties {
OR
@JsonIgnoreProperties(values = {"gender","fullName"})
public class EmployeePojoWithJsonIgnoreProperties {
We will create a POJO class in which some fields will be ignored using above annotation.
Pojo class with @JsonIgnoreProperties
package JacksonTutorials;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties({"gender","fullName"})
public class EmployeePojoWithJsonIgnoreProperties {
// private variables or data members of pojo class
private String firstName;
private String lastName;
private String gender;
private int age;
private double salary;
private boolean married;
private String fullName;
private boolean eligibleForVote;
// Getter and setter methods
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public boolean getMarried() {
return married;
}
public void setMarried(boolean married) {
this.married = married;
}
public String getFullName() {
return this.fullName;
}
public boolean getEligibleForVote() {
return this.eligibleForVote;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public void setEligibleForVote(boolean eligibleForVote) {
this.eligibleForVote = eligibleForVote;
}
}
Serialization Code
package JacksonTutorials;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SerialiDeserialExampleWithJsonIgnoreProperties {
@Test
public void serializationWithJsonIgnoreProperties() throws JsonProcessingException {
// Just create an object of Pojo class
EmployeePojoWithJsonIgnoreProperties employeePojoWithJsonIgnoreProperties = new EmployeePojoWithJsonIgnoreProperties();
employeePojoWithJsonIgnoreProperties.setFirstName("Amod");
employeePojoWithJsonIgnoreProperties.setLastName("Mahajan");
employeePojoWithJsonIgnoreProperties.setAge(29);
employeePojoWithJsonIgnoreProperties.setGender("Male");
employeePojoWithJsonIgnoreProperties.setSalary(12323.56);
employeePojoWithJsonIgnoreProperties.setMarried(false);
employeePojoWithJsonIgnoreProperties.setFullName("Animesh Prashant");
employeePojoWithJsonIgnoreProperties.setEligibleForVote(false);
// Converting a Java class object to a JSON payload as string
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(employeePojoWithJsonIgnoreProperties);
System.out.println("Serialization...");
System.out.println(employeeJson);
}
}
Output
Serialization...
{
"firstName" : "Amod",
"lastName" : "Mahajan",
"age" : 29,
"salary" : 12323.56,
"married" : false,
"eligibleForVote" : false
}
We have ignored properties “gender” and “fullName” which are not part of JSON output above.
Deserialization Code
package JacksonTutorials;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SerialiDeserialExampleWithJsonIgnoreProperties {
@Test
public void deserializationWithJsonIgnoreProperties() throws JsonMappingException, JsonProcessingException
{
String employeeString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\",\r\n" +
" \"age\" : 29,\r\n" +
" \"salary\" : 12323.56,\r\n" +
" \"married\" : false,\r\n" +
" \"fullName\" : \"Amod Mahajan Gupta\",\r\n" +
" \"eligibleForVote\" : false\r\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
//objectMapper.con
EmployeePojoWithJsonIgnoreProperties employeePojoWithJsonIgnoreProperties = objectMapper.readValue(employeeString, EmployeePojoWithJsonIgnoreProperties.class);
System.out.println("Deserialization...");
System.out.println("First name :- "+employeePojoWithJsonIgnoreProperties.getFirstName());
System.out.println("Last name :- "+employeePojoWithJsonIgnoreProperties.getLastName());
System.out.println("Age :- "+employeePojoWithJsonIgnoreProperties.getAge());
System.out.println("Gender :- "+employeePojoWithJsonIgnoreProperties.getGender());
System.out.println("Salary :- "+employeePojoWithJsonIgnoreProperties.getSalary());
System.out.println("Married :- "+employeePojoWithJsonIgnoreProperties.getMarried());
System.out.println("Eligible for vote :- "+employeePojoWithJsonIgnoreProperties.getEligibleForVote());
System.out.println("Full name :- "+employeePojoWithJsonIgnoreProperties.getFullName());
}
}
Output
Deserialization...
First name :- Amod
Last name :- Mahajan
Age :- 29
Gender :- null
Salary :- 12323.56
Married :- false
Eligible for vote :- false
Full name :- null
Similarly, during deserialization, fields “gender” and “fullName” are ignored and have default values while retrieving.
Allow fields to participate only in serialization
If we have a requirement to allow some fields of a POJO class only for serialization but not for deserialization then that we can achieve this by setting allowGetters as true for @JsonIgnoreProperties annotation. This setting will make fields read-only.
POJO with allowGetters
Note below that to add more than one elements i.e. list of properties to be ignored and value for allowGetters, you need to use with the element name.
package JacksonTutorials;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(value = {"gender","fullName"}, allowGetters = true )
public class EmployeePojoWithJsonIgnoreProperties {
// private variables or data members of pojo class
private String firstName;
private String lastName;
private String gender;
private int age;
private double salary;
private boolean married;
private String fullName;
private boolean eligibleForVote;
// Getter and setter methods
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public boolean getMarried() {
return married;
}
public void setMarried(boolean married) {
this.married = married;
}
public String getFullName() {
return this.fullName;
}
public boolean getEligibleForVote() {
return this.eligibleForVote;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public void setEligibleForVote(boolean eligibleForVote) {
this.eligibleForVote = eligibleForVote;
}
}
Serialization Code
package JacksonTutorials;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SerialiDeserialExampleWithJsonIgnoreProperties {
@Test
public void serializationWithJsonIgnoreProperties() throws JsonProcessingException {
// Just create an object of Pojo class
EmployeePojoWithJsonIgnoreProperties employeePojoWithJsonIgnoreProperties = new EmployeePojoWithJsonIgnoreProperties();
employeePojoWithJsonIgnoreProperties.setFirstName("Amod");
employeePojoWithJsonIgnoreProperties.setLastName("Mahajan");
employeePojoWithJsonIgnoreProperties.setAge(29);
employeePojoWithJsonIgnoreProperties.setGender("Male");
employeePojoWithJsonIgnoreProperties.setSalary(12323.56);
employeePojoWithJsonIgnoreProperties.setMarried(false);
employeePojoWithJsonIgnoreProperties.setFullName("Animesh Prashant");
employeePojoWithJsonIgnoreProperties.setEligibleForVote(false);
// Converting a Java class object to a JSON payload as string
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(employeePojoWithJsonIgnoreProperties);
System.out.println("Serialization...");
System.out.println(employeeJson);
}
}
Output
Serialization...
{
"firstName" : "Amod",
"lastName" : "Mahajan",
"gender" : "Male",
"age" : 29,
"salary" : 12323.56,
"married" : false,
"fullName" : "Animesh Prashant",
"eligibleForVote" : false
}
You can see in the above output fields were part of serialization as getters were allowed. You can ask that I said the above fields will be read-only but I can set value by calling setter methods. Read-only means you can not set the value using deserialization. See the next code.
Deserialization code
package JacksonTutorials;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SerialiDeserialExampleWithJsonIgnoreProperties {
@Test
public void deserializationWithJsonIgnoreProperties() throws JsonMappingException, JsonProcessingException
{
String employeeString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\",\r\n" +
" \"age\" : 29,\r\n" +
" \"salary\" : 12323.56,\r\n" +
" \"married\" : false,\r\n" +
" \"fullName\" : \"Amod Mahajan Gupta\",\r\n" +
" \"eligibleForVote\" : false\r\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
//objectMapper.con
EmployeePojoWithJsonIgnoreProperties employeePojoWithJsonIgnoreProperties = objectMapper.readValue(employeeString, EmployeePojoWithJsonIgnoreProperties.class);
System.out.println("Deserialization...");
System.out.println("First name :- "+employeePojoWithJsonIgnoreProperties.getFirstName());
System.out.println("Last name :- "+employeePojoWithJsonIgnoreProperties.getLastName());
System.out.println("Age :- "+employeePojoWithJsonIgnoreProperties.getAge());
System.out.println("Gender :- "+employeePojoWithJsonIgnoreProperties.getGender());
System.out.println("Salary :- "+employeePojoWithJsonIgnoreProperties.getSalary());
System.out.println("Married :- "+employeePojoWithJsonIgnoreProperties.getMarried());
System.out.println("Eligible for vote :- "+employeePojoWithJsonIgnoreProperties.getEligibleForVote());
System.out.println("Full name :- "+employeePojoWithJsonIgnoreProperties.getFullName());
}
}
Output
Deserialization...
First name :- Amod
Last name :- Mahajan
Age :- 29
Gender :- null
Salary :- 12323.56
Married :- false
Eligible for vote :- false
Full name :- null
We have values for fields “Gender” and “Full name” in JSON string but while deserialization they were ignored as setters were not allowed.
Allow fields to participate only in deserialization
If we have a requirement to allow some fields of a POJO class only for deserialization but not for serialization then that we can achieve this by setting allowSetters as true for @JsonIgnoreProperties annotation. This setting will make fields write-only.
POJO with allowSetters
package JacksonTutorials;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(value = {"gender","fullName"}, allowSetters = true )
public class EmployeePojoWithJsonIgnoreProperties {
// private variables or data members of pojo class
private String firstName;
private String lastName;
private String gender;
private int age;
private double salary;
private boolean married;
private String fullName;
private boolean eligibleForVote;
// Getter and setter methods
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public boolean getMarried() {
return married;
}
public void setMarried(boolean married) {
this.married = married;
}
public String getFullName() {
return this.fullName;
}
public boolean getEligibleForVote() {
return this.eligibleForVote;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public void setEligibleForVote(boolean eligibleForVote) {
this.eligibleForVote = eligibleForVote;
}
}
Serialization Code
package JacksonTutorials;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SerialiDeserialExampleWithJsonIgnoreProperties {
@Test
public void serializationWithJsonIgnoreProperties() throws JsonProcessingException {
// Just create an object of Pojo class
EmployeePojoWithJsonIgnoreProperties employeePojoWithJsonIgnoreProperties = new EmployeePojoWithJsonIgnoreProperties();
employeePojoWithJsonIgnoreProperties.setFirstName("Amod");
employeePojoWithJsonIgnoreProperties.setLastName("Mahajan");
employeePojoWithJsonIgnoreProperties.setAge(29);
employeePojoWithJsonIgnoreProperties.setGender("Male");
employeePojoWithJsonIgnoreProperties.setSalary(12323.56);
employeePojoWithJsonIgnoreProperties.setMarried(false);
employeePojoWithJsonIgnoreProperties.setFullName("Animesh Prashant");
employeePojoWithJsonIgnoreProperties.setEligibleForVote(false);
// Converting a Java class object to a JSON payload as string
ObjectMapper objectMapper = new ObjectMapper();
String employeeJson = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(employeePojoWithJsonIgnoreProperties);
System.out.println("Serialization...");
System.out.println(employeeJson);
}
}
Output
Serialization...
{
"firstName" : "Amod",
"lastName" : "Mahajan",
"age" : 29,
"salary" : 12323.56,
"married" : false,
"eligibleForVote" : false
}
Deserialization code
package JacksonTutorials;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SerialiDeserialExampleWithJsonIgnoreProperties {
@Test
public void deserializationWithJsonIgnoreProperties() throws JsonMappingException, JsonProcessingException
{
String employeeString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\",\r\n" +
" \"age\" : 29,\r\n" +
" \"salary\" : 12323.56,\r\n" +
" \"married\" : false,\r\n" +
" \"fullName\" : \"Amod Mahajan Gupta\",\r\n" +
" \"eligibleForVote\" : false\r\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
//objectMapper.con
EmployeePojoWithJsonIgnoreProperties employeePojoWithJsonIgnoreProperties = objectMapper.readValue(employeeString, EmployeePojoWithJsonIgnoreProperties.class);
System.out.println("Deserialization...");
System.out.println("First name :- "+employeePojoWithJsonIgnoreProperties.getFirstName());
System.out.println("Last name :- "+employeePojoWithJsonIgnoreProperties.getLastName());
System.out.println("Age :- "+employeePojoWithJsonIgnoreProperties.getAge());
System.out.println("Gender :- "+employeePojoWithJsonIgnoreProperties.getGender());
System.out.println("Salary :- "+employeePojoWithJsonIgnoreProperties.getSalary());
System.out.println("Married :- "+employeePojoWithJsonIgnoreProperties.getMarried());
System.out.println("Eligible for vote :- "+employeePojoWithJsonIgnoreProperties.getEligibleForVote());
System.out.println("Full name :- "+employeePojoWithJsonIgnoreProperties.getFullName());
}
}
Output
Deserialization...
First name :- Amod
Last name :- Mahajan
Age :- 29
Gender :- Male
Salary :- 12323.56
Married :- false
Eligible for vote :- false
Full name :- Amod Mahajan Gupta
Since setters were allowed for ignored fields “gender” and “fullName”, they have participated in the deserialization process but not in serialization.
Ignore Unknown Properties During Deserialization Using @JsonIgnoreProperties
Understand the meaning of known and unknown properties
Before we go for an actual topic let’s understand the meaning of words “known” and “unknown” properties. If a property is included in a POJO class or if a property exists in POJO class then it is a known property for that POJO class. A property that is not known to a POJO class is unknown. Let’s see the behavior when a known property is missing during deserialization.
Missing known properties during deserialization
Suppose we have a POJO as below:-
package JacksonTutorials;
public class Employee {
// private variables or data members of pojo class
private String firstName;
private String lastName;
private String gender;
private int age;
private double salary;
private boolean married;
// Getter and setter methods
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public boolean getMarried() {
return married;
}
public void setMarried(boolean married) {
this.married = married;
}
}
Deserialize with some missing known properties
We will not include all known properties in JSON during deserialization as shown below:-
package JacksonTutorials;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class DeserializationWithMissingKnownProperties {
@Test
public void deserializationWithMissingKnownProperties() throws JsonMappingException, JsonProcessingException
{
String employeeString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\"\r\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
//objectMapper.con
Employee employee = objectMapper.readValue(employeeString, Employee.class);
System.out.println("Deserialization...");
System.out.println("First name :- "+employee.getFirstName());
System.out.println("Last name :- "+employee.getLastName());
System.out.println("Age :- "+employee.getAge());
System.out.println("Gender :- "+employee.getGender());
System.out.println("Salary :- "+employee.getSalary());
System.out.println("Married :- "+employee.getMarried());
}
}
Output
Deserialization...
First name :- Amod
Last name :- Mahajan
Age :- 0
Gender :- Male
Salary :- 0.0
Married :- false
In JSON string three known properties are missing i.e. age, salary, and married. After deserialization, these fields have a default value. There is no error thrown for those missing values for known properties.
Additional unknown properties during deserialization
In the previous example, we had missing known properties in our JSON. Now we will have some additional unknown properties in JSON and observe behavior during deserialization.
Deserialize with some additional unknown properties
package JacksonTutorials;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class DeserializationWithAdditionalUnknownProperties {
@Test
public void deserializationWithAdditionalUnknownProperties() throws JsonMappingException, JsonProcessingException
{
String employeeString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\",\r\n" +
" \"age\" : 29,\r\n" +
" \"salary\" : 12323.56,\r\n" +
" \"married\" : false,\r\n" +
" \"fullName\" : \"Amod Mahajan Gupta\",\r\n" +
" \"eligibleForVote\" : false\r\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
//objectMapper.con
Employee employee = objectMapper.readValue(employeeString, Employee.class);
System.out.println("Deserialization...");
System.out.println("First name :- "+employee.getFirstName());
System.out.println("Last name :- "+employee.getLastName());
System.out.println("Age :- "+employee.getAge());
System.out.println("Gender :- "+employee.getGender());
System.out.println("Salary :- "+employee.getSalary());
System.out.println("Married :- "+employee.getMarried());
}
}
There are some additional unknown fields that are present in JSON string i.e. fullName and eligibleForVote. When we try to deserialize above JSON string we get the output as below:-
Output
[RemoteTestNG] detected TestNG version 7.0.1
FAILED: deserializationWithMissingKnownProperties
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "fullName" (class JacksonTutorials.Employee), not marked as ignorable (6 known properties: "lastName", "married", "salary", "firstName", "age", "gender"])
at [Source: (String)"{
"firstName" : "Amod",
"lastName" : "Mahajan",
"gender" : "Male",
"age" : 29,
"salary" : 12323.56,
"married" : false,
"fullName" : "Amod Mahajan Gupta",
"eligibleForVote" : false
}"; line: 8, column: 17] (through reference chain: JacksonTutorials.Employee["fullName"])
As you can see deserialization failed at the encounter of first unknown property “fullName”. It also gives a list of all known properties.
@JsonIgnoreProperties – ignoreUnknown
We want that if there are any unknown fields in JSON then the deserialization process should ignore them. This we can achieve using a property called “ignoreUnknown” of @JsonIgnoreProperties annotation. It accepts a boolean value and the default value is false. To ignore unknown property we need to set it true at the class level.
POJO with ignoring unknown properties
package JacksonTutorials;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Employee {
// private variables or data members of pojo class
private String firstName;
private String lastName;
private String gender;
private int age;
private double salary;
private boolean married;
// Getter and setter methods
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public boolean getMarried() {
return married;
}
public void setMarried(boolean married) {
this.married = married;
}
}
Deserialize with some additional unknown properties
There is no change in the above deserialization code.
Output
Deserialization...
First name :- Amod
Last name :- Mahajan
Age :- 29
Gender :- Male
Salary :- 12323.56
Married :- false
Ignore Unknown Properties During Deserialization Using ObjectMapper – Jackson API
Prerequisite
Ignoring unknown properties using @JsonIgnoreProperties
We can ignore unknown properties during deserialization using an annotation called @JsonIgnoreProperties provided by Jackson. We have covered this topic already. Refer that below:-
Understand the meaning of known and unknown properties
Before we go for an actual topic let’s understand the meaning of words “known” and “unknown” properties. If a property is included in a POJO class or if a property exists in POJO class then it is a known property for that POJO class. A property that is not known to a POJO class is unknown. Let’s see the behavior when a known property is missing during deserialization.
Missing known properties during deserialization
Suppose we have a POJO as below:-
package JacksonTutorials;
public class Employee {
// private variables or data members of pojo class
private String firstName;
private String lastName;
private String gender;
private int age;
private double salary;
private boolean married;
// Getter and setter methods
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public boolean getMarried() {
return married;
}
public void setMarried(boolean married) {
this.married = married;
}
}
Deserialize with some missing known properties
We will not include all known properties in JSON during deserialization as shown below:-
package JacksonTutorials;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class DeserializationWithMissingKnownProperties {
@Test
public void deserializationWithMissingKnownProperties() throws JsonMappingException, JsonProcessingException
{
String employeeString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\"\r\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
//objectMapper.con
Employee employee = objectMapper.readValue(employeeString, Employee.class);
System.out.println("Deserialization...");
System.out.println("First name :- "+employee.getFirstName());
System.out.println("Last name :- "+employee.getLastName());
System.out.println("Age :- "+employee.getAge());
System.out.println("Gender :- "+employee.getGender());
System.out.println("Salary :- "+employee.getSalary());
System.out.println("Married :- "+employee.getMarried());
}
}
Output
Deserialization...
First name :- Amod
Last name :- Mahajan
Age :- 0
Gender :- Male
Salary :- 0.0
Married :- false
In JSON string three known properties are missing i.e. age, salary, and married. After deserialization, these fields have a default value. There is no error thrown for those missing values for known properties.
Additional unknown properties during deserialization
In the previous example, we had missing known properties in our JSON. Now we will have some additional unknown properties in JSON and observe behavior during deserialization.
Deserialize with some additional unknown properties
package JacksonTutorials;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class DeserializationWithAdditionalUnknownProperties {
@Test
public void deserializationWithAdditionalUnknownProperties() throws JsonMappingException, JsonProcessingException
{
String employeeString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\",\r\n" +
" \"age\" : 29,\r\n" +
" \"salary\" : 12323.56,\r\n" +
" \"married\" : false,\r\n" +
" \"fullName\" : \"Amod Mahajan Gupta\",\r\n" +
" \"eligibleForVote\" : false\r\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
//objectMapper.con
Employee employee = objectMapper.readValue(employeeString, Employee.class);
System.out.println("Deserialization...");
System.out.println("First name :- "+employee.getFirstName());
System.out.println("Last name :- "+employee.getLastName());
System.out.println("Age :- "+employee.getAge());
System.out.println("Gender :- "+employee.getGender());
System.out.println("Salary :- "+employee.getSalary());
System.out.println("Married :- "+employee.getMarried());
}
}
There are some additional unknown fields that are present in JSON string i.e. fullName and eligibleForVote. When we try to deserialize above JSON string we get the output as below:-
Output
[RemoteTestNG] detected TestNG version 7.0.1
FAILED: deserializationWithMissingKnownProperties
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "fullName" (class JacksonTutorials.Employee), not marked as ignorable (6 known properties: "lastName", "married", "salary", "firstName", "age", "gender"])
at [Source: (String)"{
"firstName" : "Amod",
"lastName" : "Mahajan",
"gender" : "Male",
"age" : 29,
"salary" : 12323.56,
"married" : false,
"fullName" : "Amod Mahajan Gupta",
"eligibleForVote" : false
}"; line: 8, column: 17] (through reference chain: JacksonTutorials.Employee["fullName"])
As you can see deserialization failed at the encounter of first unknown property “fullName”. It also gives a list of all known properties.
ObjectMapper configuration to ignore unknown properties
In this post, we ignored unknown properties during deserialization using @JsonIgnoreProperties which works at the class level. If you do not have access to POJO class or you are not allowed to edit POJO classes then you can go for ObjectMapper level configuration.
We need to use enum DeserializationFeature to set the value of “FAIL_ON_UNKNOWN_PROPERTIES” as false. There is no need to make any change in the POJO class.
ObjectMapper Configuration Code
package JacksonTutorials;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class DeserializationWithAdditionalUnknownPropertiesObjectMapper {
@Test
public void deserializationWithAdditionalUnknownProperties() throws JsonMappingException, JsonProcessingException
{
String employeeString = "{\r\n" +
" \"firstName\" : \"Amod\",\r\n" +
" \"lastName\" : \"Mahajan\",\r\n" +
" \"gender\" : \"Male\",\r\n" +
" \"age\" : 29,\r\n" +
" \"salary\" : 12323.56,\r\n" +
" \"married\" : false,\r\n" +
" \"fullName\" : \"Amod Mahajan Gupta\",\r\n" +
" \"eligibleForVote\" : false\r\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//objectMapper.con
Employee employee = objectMapper.readValue(employeeString, Employee.class);
System.out.println("Deserialization...");
System.out.println("First name :- "+employee.getFirstName());
System.out.println("Last name :- "+employee.getLastName());
System.out.println("Age :- "+employee.getAge());
System.out.println("Gender :- "+employee.getGender());
System.out.println("Salary :- "+employee.getSalary());
System.out.println("Married :- "+employee.getMarried());
}
}
Output
Deserialization...
First name :- Amod
Last name :- Mahajan
Age :- 29
Gender :- Male
Salary :- 12323.56
Married :- false
You can see how deserialization happened successfully with unknown properties present in the JSON string.
Get All Keys From A Nested JSON Object
Introduction
We may get a nested JSON Object which may be dynamic. The dynamic response may include conditional keys and values. For example- A business class ticket will have more benefits than an economy class ticket. If we have an API that books a ticket then obviously we will have different JSON (Suppose JSON as format) response. The same may apply for the JSON request body as well.
In short, you may need to get all keys from a normal JSON object and nested JSON object. This is an interview question as well. You may be asked just to check the presence of a key in JSON response.
Prerequisite
Required Java Library
Since we are using Jackson API of Java for this example, make sure you have the latest dependency of Jackson Databind in your project classpath. I have used below Jackson dependency for this post:-
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.2</version>
</dependency>
Simple JSON Object
Example JSON Object
{
"firstName": "Animesh",
"lastName": "Prashant"
}
Using Java MAP
As we know that we can deserialize a JSON Object to Java Map easily. As Map is a key-value pair we can get all keys easily.
package JacksonTutorials;
import java.util.Map;
import java.util.Set;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class GetAllKeysFromJsonObject {
@Test
public void getAllKeysFromJsonObjectUsingMap() throws JsonMappingException, JsonProcessingException {
String jsonObject = "{\r\n" + " \"firstName\": \"Animesh\",\r\n" + " \"lastName\": \"Prashant\"\r\n" + "}";
ObjectMapper objectMapper = new ObjectMapper();
// Deserializing into a Map
Map<String, String> parsedJsonObject = objectMapper.readValue(jsonObject,
new TypeReference<Map<String, String>>() {
});
// Get all keys
Set<String> allKeys = parsedJsonObject.keySet();
System.out.println("All keys are : ");
allKeys.stream().forEach(k -> System.out.println(k));
}
}
Output
All keys are :
firstName
lastName
Using JsonNode
We need to use a method “readTree()” provided by “ObjectMapper” class which is overloaded. Method “readTree()” is to deserialize JSON content as tree expressed using a set of JsonNode instances. JsonNode is a base class for all JSON nodes, which form the basis of the JSON Tree Model that Jackson implements. One way to think of these nodes is to consider them similar to DOM nodes in XML DOM trees. Source – Jackson Java Doc
package JacksonTutorials;
import java.util.Iterator;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class GetAllKeysFromJsonObject {
@Test
public void getAllKeysFromJsonObjectUsingObjectMapper() throws JsonMappingException, JsonProcessingException
{
String jsonObject = "{\r\n" +
" \"firstName\": \"Animesh\",\r\n" +
" \"lastName\": \"Prashant\"\r\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
// Converting JSON Object string to JsonNode
JsonNode parsedJsonObject = objectMapper.readTree(jsonObject);
// Get all fields or keys
Iterator<String> allKeys= parsedJsonObject.fieldNames();
System.out.println("All keys are : ");
allKeys.forEachRemaining(k -> System.out.println(k));
}
}
Output
All keys are :
firstName
lastName
Nested JSON Object
Example JSON Object
{
"firstName": "Animesh",
"lastName": "Prashant",
"address": {
"city": "Katihar",
"State": "Bihar"
}
}
If we use the above logic to get keys from the nested JSON object we will not get keys “city” and “state”. So here we need to put extra logic to check if the value of a key is a JSON Object. If yes then we need to get keys from that as well.
Using Map
When we deserialize a JSON Objects to Map then it is actually an instance of LinkedHashMap. We can use LinkedHashMap directly instead of Map.
package JacksonTutorials;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class GetAllKeysFromJsonObject {
@Test
public void getAllKeysFromNestedJsonObjectUsingMap() throws JsonMappingException, JsonProcessingException
{
String jsonObject = "{\r\n" +
" \"firstName\": \"Animesh\",\r\n" +
" \"lastName\": \"Prashant\",\r\n" +
" \"address\": {\r\n" +
" \"city\": \"Katihar\",\r\n" +
" \"State\": \"Bihar\"\r\n" +
" }\r\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
// Deserialize into Map
Map<String,Object> parsedJsonObject = objectMapper.readValue(jsonObject, new TypeReference<Map<String, Object>>(){});
// Get all keys
Set<String> allKeys = parsedJsonObject.keySet();
// Iterate keys
allKeys.stream().forEach(key -> {
Object value = parsedJsonObject.get(key);
// If value is a String. You may need to add more if value is of different types
if(value instanceof String)
System.out.println(key);
// If value is another JSON Object which will be LinkedHashMap. You can see this while debugging
else if(value instanceof LinkedHashMap<?, ?>)
{
@SuppressWarnings("unchecked")
Set<String> allKeysOfNestedJsonObject = ((LinkedHashMap<String, ?>)value).keySet();
allKeysOfNestedJsonObject.stream().forEach(k->System.out.println(k));
}
});
}
}
Output
firstName
lastName
city
State
Using JsonNode
package JacksonTutorials;
import java.util.Iterator;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
public class GetAllKeysFromJsonObject {
@Test
public void getAllKeysFromNestedJsonObjectUsingJsonNode() throws JsonMappingException, JsonProcessingException
{
String jsonObject = "{\r\n" +
" \"firstName\": \"Animesh\",\r\n" +
" \"lastName\": \"Prashant\",\r\n" +
" \"address\": {\r\n" +
" \"city\": \"Katihar\",\r\n" +
" \"State\": \"Bihar\"\r\n" +
" }\r\n" +
"}";
ObjectMapper objectMapper = new ObjectMapper();
JsonNode parsedJsonObject = objectMapper.readTree(jsonObject);
Iterator<String> allKeys= parsedJsonObject.fieldNames();
allKeys.forEachRemaining(k -> {
Object value = parsedJsonObject.get(k);
// TextNode can be related to String from previous example
// You may need to add IntNode or BooleanNode for different types of values
if(value instanceof TextNode)
System.out.println(k);
// ObjectNode can be related to LinkedHashMap from previous example
else if(value instanceof ObjectNode)
{
Iterator<String> keyss = ((ObjectNode)value).fieldNames();
keyss.forEachRemaining(ke -> System.out.println(ke));
}
});
}
}
Output
firstName
lastName
city
State
Fetch Value From JSON Object Using JsonNode – Jackson – Get() & Path() Methods
Creating POJO classes for parsing a JSON to fetch values may not be easy all the time especially when you have lengthy nested JSON. Instead, we can use the tree structure of a JSON.
Prerequisite
Since we are using Jackson API of Java for this example, make sure you have the latest dependency of Jackson Databind in your project classpath. I have used below Jackson dependency for this post:-
com.fasterxml.jackson.core
jackson-databind
2.11.1
Tree representation of JSON Object
Example JSON Object
{
"firstName": "Amod",
"lastName": "Mahajan",
"married": false,
"salary": 2000.54,
"addressPin": 45324
}
Tree Structure
You can use this site to view the tree representation of a JSON. A tree representation of the above example JSON will look as below:-
A tree structure helps you to navigate to a node easily.
Deserialize a JSON Object to Tree
We need to use class ObjectMapper provided by Jackson API. ObjectMapper class provides a method “readTree()” which is responsible to deserialize JSON content as tree expressed using a set of JsonNode instances.
We can get the value of a node using get() and path() methods of JsonNode class. We need to extract value with appropriate data types after using get() and path() methods.
Example Program
package JsonNodeJackson;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class ParseJsonObjectToReadValues {
@Test
public void parseJsonObjectToReadValues() throws JsonMappingException, JsonProcessingException
{
String jsonObject = "{\r\n" +
" \"firstName\": \"Amod\",\r\n" +
" \"lastName\": \"Mahajan\",\r\n" +
" \"married\": false,\r\n" +
" \"salary\": 2000.54,\r\n" +
" \"addressPin\": 45324\r\n" +
"}";
// Creating an instance of ObjectMapper class
ObjectMapper objectMapper = new ObjectMapper();
// Get tree representation of json
JsonNode jsonTree = objectMapper.readTree(jsonObject);
// Get value of firstName as string
String firstName = jsonTree.get("firstName").asText();
String lastName = jsonTree.get("lastName").asText();
// Get value of married as boolean
boolean married = jsonTree.get("married").asBoolean();
double salary = jsonTree.get("salary").asDouble();
long addressPin = jsonTree.get("addressPin").asLong();
System.out.println("FirstName is : "+firstName);
System.out.println("LastName is : "+lastName);
System.out.println("Married is : "+married);
System.out.println("Salary is : "+salary);
System.out.println("Addresspin is: "+addressPin);
}
}
Output
FirstName is : Amod
LastName is : Mahajan
Married is : false
Salary is : 2000.54
Addresspin is: 45324
Observe above that I used asText(), asBoolean() methods to parse value appropriately. If you are not sure what type of value is held by node then you can use methods like isTextual(), isBoolean(), etc.
System.out.println(jsonTree.get("firstName").isTextual());
System.out.println(jsonTree.get("lastName").isTextual());
System.out.println(jsonTree.get("married").isBoolean());
System.out.println(jsonTree.get("salary").isDouble());
System.out.println(jsonTree.get("addressPin").isLong());
You will not get an exception if you are not parsing to correct data types. For example – If you parse a string to boolean using asBoolean() then it works as below as per offical Java doc of Jackson API:-
Method that will try to convert value of this node to a Java boolean. JSON booleans map naturally; integer numbers other than 0 map to true, and 0 maps to false and Strings ‘true’ and ‘false’ map to corresponding values. If representation cannot be converted to a boolean value (including structured types like Objects and Arrays), default value of false will be returned; no exceptions are thrown.
Similarly for asLong():-
Method that will try to convert value of this node to a Java long. Numbers are coerced using default Java rules; booleans convert to 0 (false) and 1 (true), and Strings are parsed using default Java language integer parsing rules. If representation cannot be converted to an long (including structured types like Objects and Arrays), default value of 0 will be returned; no exceptions are thrown.
Retrieving value for a non-existing node
When you are trying to retrieve a value method for a node that doesn’t exist using get() then you will get NullPointerException.
// Retrieving value of non-existing key
System.out.println(jsonTree.get("nonExistingNode").asText());
You can use overloaded methods such as asText(String defaultValue), asInt(int defaultValue) to handle these unexpected null values. These methods will handle if a node has explicit value as null. But to use these methods you need to use an alternative of get() method called path() method. With get() if you use above methods then also you will get NullPointerException.
As per the official doc – path() method is similar to get(String), except that instead of returning null if no such value exists (due to this node not being an object, or object not having value for the specified field), a “missing node” will be returned. This allows for convenient and safe chained access via path calls.
// Retrieving value of non-existing key
String s = jsonTree.get("nonExistingNode").asText("Default Value");
System.out.println(s);
The output will be a NullPointerException.
// Retrieving value of non-existing key using path
String s1 = jsonTree.path("nonExistingNode").asText("Default value");
System.out.println(s1);
The Output will be “Default value”.
Fetch Value From Nested JSON Object Using JsonNode – Jackson – At() Method
Tree representation of JSON Object
Example JSON Object
{
"firstName": "Amod",
"lastName": "Mahajan",
"married": false,
"salary": 2000.54,
"addressPin": 45324,
"skill" :{
"Java" :"Intermediate",
"Selenium" :"Intermediate",
"Javascript" :"Beginner"
}
}
Tree Structure
You can use this site to view the tree representation of a JSON. A tree representation of the above example JSON will look as below:-
A tree structure helps you to navigate to a node easily using its path. We can relate this path as XPath for HTML DOM elements. Ex:- To reach “Java” node we need to opt for path as object(main node)/skill/java.
Deserialize a JSON Object to Tree
We need to use class ObjectMapper provided by Jackson API. ObjectMapper class provides a method “readTree()” which is responsible to deserialize JSON content as tree expressed using a set of JsonNode instances.
We can get the value of a node using get() and path() methods of JsonNode class. We need to extract value with appropriate data types after using get() and path() methods.
We have seen the above concept already in the previous post here. In the previous example, we have a simple JSON object with 1 to 1 mapping. For this post, I have taken a nested JSON object as an example where a key contains another JSON object.
We can chain get() or path() methods to traverse to a nested JSON object key as below:-
// Using chaining of get() methods
String javaLevel = jsonTree.get("skills").get("Java").asText();
System.out.println(javaLevel);
// Using chaining of path() methods
String javaLevel2 = jsonTree.path("skills").path("Java").asText();
System.out.println(javaLevel2);
This may be longer in the case of a deeply nested JSON object. We can use at() method instead. We need to pass the path of target node similar to a file path.
// Using pattern expression with at()
String javaLevel3 = jsonTree.at("/skills/Java").asText();
System.out.println(javaLevel3);
at() method will never return null if no matching node exists. In that case, it will return a node for which isMissingNode() method returns true.
Example Program
package JsonNodeJackson;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class ParseNestedJsonObjectToReadValues {
@Test
public void parseJsonObjectToReadValues() throws JsonMappingException, JsonProcessingException
{
String jsonObject = "{\r\n" +
" \"firstName\": \"Amod\",\r\n" +
" \"lastName\": \"Mahajan\",\r\n" +
" \"married\": false,\r\n" +
" \"salary\": 2000.54,\r\n" +
" \"addressPin\": 45324,\r\n" +
" \"skills\" :{\r\n" +
" \"Java\" :\"Intermediate\",\r\n" +
" \"Selenium\" :\"Intermediate\",\r\n" +
" \"Javascript\" :\"Beginner\"\r\n" +
" }\r\n" +
"}";
// Creating an instance of ObjectMapper class
ObjectMapper objectMapper = new ObjectMapper();
// Get tree representation of json
JsonNode jsonTree = objectMapper.readTree(jsonObject);
// Using chaining of get() methods
String javaLevel = jsonTree.get("skills").get("Java").asText();
System.out.println(javaLevel);
// Using chaining of path() methods
String javaLevel2 = jsonTree.path("skills").path("Java").asText();
System.out.println(javaLevel2);
// Using pattern expression with at()
String javaLevel3 = jsonTree.at("/skills/Java").asText();
System.out.println(javaLevel3);
}
}
Output
Intermediate
Intermediate
Intermediate
Fetch Value From JSON Array Using JsonNode – Jackson – Get() & Path() Methods
Tree representation of JSON Array
Example JSON Array
Tree representation of JSON Array
You can use this site to view the tree representation of a JSON Array. A tree representation of the above example JSON array will look as below:-
A JSON array may be a collection of JSON objects or JSON arrays. Below is also a valid example of a JSON array.
[
[
{
"firstName": "Amod",
"lastName": "Mahajan",
"age": 28,
"isMarried": false,
"salary": 23456.54
},
{
"firstName": "Rahul",
"lastName": "Arora",
"age": 32,
"isMarried": true,
"salary": 33456.54
}
],
[
{
"firstName": "Amod",
"lastName": "Mahajan",
"age": 28,
"isMarried": false,
"salary": 23456.54
},
{
"firstName": "Rahul",
"lastName": "Arora",
"age": 32,
"isMarried": true,
"salary": 33456.54
}
]
]
Deserialize a JSON Array to Tree
We need to use class ObjectMapper provided by Jackson API. ObjectMapper class provides a method “readTree()” which is responsible to deserialize JSON content as tree expressed using a set of JsonNode instances.
We can get the value of a node using get() and path() methods of JsonNode class. We need to extract value with appropriate data types after using get() and path() methods.
We just need to use an index to fetch an element of an array which is the core concept of an array.
// Creating an instance of ObjectMapper class
ObjectMapper objectMapper = new ObjectMapper();
// Get tree representation of json
JsonNode jsonTree = objectMapper.readTree(jsonArray);
// Get first json object and storing
JsonNode firstJsonObject = jsonTree.get(0);
// Get second json object and storing
JsonNode secondJsonObject = jsonTree.get(1);
If you are not sure if parsed JSON tree is a JSON object or JSON tree then you can use instanceof as below:-
// To know if tree is a JSON object or JSON array
System.out.println("Is parsed JSOn tree a JSON Object?"+ Boolean.toString(jsonTree instanceof ObjectNode));
System.out.println("Is parsed JSOn tree a JSON Array?"+ Boolean.toString(jsonTree instanceof ArrayNode));
Remember that JsonNode is the parent class of both ObjectNode and ArrayNode.
Remaining all concepts are the same as we discussed in the posts mentioned under prerequisite headers. If you are sure of if JSON is an object or array we can directly cast and store in that type.
ArrayNode jsonTree = (ArrayNode) objectMapper.readTree(jsonArray);
ArrayNode jsonTree = (ArrayNode) objectMapper.readTree(jsonArray); |
Example Program
package JsonNodeJackson;
import org.testng.annotations.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
public class ParseJsonArrayToReadValues {
@Test
public void parseJsonArrayToReadValues() throws JsonMappingException, JsonProcessingException
{
String jsonArray = "[\r\n" +
" {\r\n" +
" \"firstName\": \"Amod\",\r\n" +
" \"lastName\": \"Mahajan\",\r\n" +
" \"age\": 28,\r\n" +
" \"isMarried\": false,\r\n" +
" \"salary\": 23456.54\r\n" +
" },\r\n" +
" {\r\n" +
" \"firstName\": \"Rahul\",\r\n" +
" \"lastName\": \"Arora\",\r\n" +
" \"age\": 32,\r\n" +
" \"isMarried\": true,\r\n" +
" \"salary\": 33456.54\r\n" +
" }\r\n" +
"]";
// Creating an instance of ObjectMapper class
ObjectMapper objectMapper = new ObjectMapper();
// Get tree representation of json
JsonNode jsonTree = objectMapper.readTree(jsonArray);
// To know if tree is a JSON object or JSON array
System.out.println("Is parsed JSOn tree a JSON Object?"+ Boolean.toString(jsonTree instanceof ObjectNode));
System.out.println("Is parsed JSOn tree a JSON Array?"+ Boolean.toString(jsonTree instanceof ArrayNode));
// Get first json object and storing
JsonNode firstJsonObject = jsonTree.get(0);
// Get second json object and storing
JsonNode secondJsonObject = jsonTree.get(1);
// Get value of firstName as string
String firstName = firstJsonObject.get("firstName").asText();
String lastName = firstJsonObject.get("lastName").asText();
// Get value of married as boolean
int age = firstJsonObject.get("age").asInt();
boolean married = firstJsonObject.get("isMarried").asBoolean();
double salary = firstJsonObject.get("salary").asLong();
System.out.println("FirstName is : "+firstName);
System.out.println("LastName is : "+lastName);
System.out.println("Age is : "+age);
System.out.println("Maritial status is : "+married);
System.out.println("Salary is: "+salary);
// Get value of firstName as string
firstName = secondJsonObject.get("firstName").asText();
lastName = secondJsonObject.get("lastName").asText();
// Get value of married as boolean
age = secondJsonObject.get("age").asInt();
married = secondJsonObject.get("isMarried").asBoolean();
salary = secondJsonObject.get("salary").asLong();
System.out.println("FirstName is : "+firstName);
System.out.println("LastName is : "+lastName);
System.out.println("Age is : "+age);
System.out.println("Maritial status is : "+married);
System.out.println("Salary is: "+salary);
}
}
Output
Is parsed JSOn tree a JSON Object?false
Is parsed JSOn tree a JSON Array?true
FirstName is : Amod
LastName is : Mahajan
Age is : 28
Maritial status is : false
Salary is: 23456.0
FirstName is : Rahul
LastName is : Arora
Age is : 32
Maritial status is : true
Salary is: 33456.0
Fetch Value From Nested JSON Array Using JsonNode – Jackson – At() Method
How To Pass Headers In Rest Assured Requests
How To Retrieve Single And MultiValue Headers From Response Using Rest Assured
How To Set Content-Type For Request In Rest Assured
Content-Type is a header that indicates the media type or MIME( Multipurpose Internet Mail Extensions ) type or type of a file. When we hit any POST or PUT API requests we may need to pass a payload. That payload can be in any supported format by API such as XML, JSON, etc. We need to use the Content-Type header to let the server know the format of the payload for a request which will be sent.
Similarly, Content-Type for response indicates the format of the response returned. It is not mandatory that the Content-Type of request and response should be the same.
Prerequisite
I have used below dependency of Rest Assured library for this post:-
<!-- https://mvnrepository.com/artifact/io.rest-assured/rest-assured -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>4.3.1</version>
<scope>test</scope>
</dependency>
Why we may need to set Content-Type explicitly for a request?
Postman tool automatically adds a Content-Type header based on the request body we select. For example, if select request body format as JSON then Postman will add automatically a header named “Content-Type” with value as “application/json“. This does not happen automatically in Rest Assured and you may get an unexpected response as a server may not identify the format of payload.
Let’s see an example where we are not setting up Content-Type explicitly. We are passing a payload which is in a JSON format.
package RestAssuredBasicConcepts;
import org.testng.annotations.Test;
import io.restassured.RestAssured;
public class SetContentTypeForRequest {
@Test
public void WIthoutSettingContentType()
{
RestAssured
.given()
.log()
.all()
.body("{\r\n" +
" \"firstname\" : \"Jim\",\r\n" +
" \"lastname\" : \"Brown\",\r\n" +
" \"totalprice\" : 111,\r\n" +
" \"depositpaid\" : true,\r\n" +
" \"bookingdates\" : {\r\n" +
" \"checkin\" : \"2018-01-01\",\r\n" +
" \"checkout\" : \"2019-01-01\"\r\n" +
" },\r\n" +
" \"additionalneeds\" : \"Breakfast\"\r\n" +
"}")
.post("https://restful-booker.herokuapp.com/booking")
.then()
.log()
.all();
}
}
Output
[RemoteTestNG] detected TestNG version 7.0.1
Request method: POST
Request URI: https://restful-booker.herokuapp.com/booking
Proxy: <none>
Request params: <none>
Query params: <none>
Form params: <none>
Path params: <none>
Headers: Accept=*/*
Content-Type=text/plain; charset=ISO-8859-1
Cookies: <none>
Multiparts: <none>
Body:
{
"firstname" : "Jim",
"lastname" : "Brown",
"totalprice" : 111,
"depositpaid" : true,
"bookingdates" : {
"checkin" : "2018-01-01",
"checkout" : "2019-01-01"
},
"additionalneeds" : "Breakfast"
}
HTTP/1.1 500 Internal Server Error
Server: Cowboy
Connection: keep-alive
X-Powered-By: Express
Content-Type: text/plain; charset=utf-8
Content-Length: 21
Etag: W/"15-/6VXivhc2MKdLfIkLcUE47K6aH0"
Date: Tue, 29 Sep 2020 07:42:56 GMT
Via: 1.1 vegur
Internal Server Error
PASSED: WIthoutSettingContentType
===============================================
Default test
Tests run: 1, Failures: 0, Skips: 0
===============================================
===============================================
Default suite
Total tests run: 1, Passes: 1, Failures: 0, Skips: 0
===============================================
We are passing a JSON payload to request but observe in output that it takes Content-Type as “text/plain” and return a response as “Internal server error“. Sever could not understand the correct format of the request payload and failed. This is the reason we should set Content-Type for a request properly.
Adding Content-Type to a request in Rest Assured
As Content-Type is a header we can pass it as a key-value pair using methods discussed here. An example is as below:-
package RestAssuredBasicConcepts;
import org.testng.annotations.Test;
import io.restassured.RestAssured;
public class SetContentTypeForRequest {
@Test
public void settingContentTypeAsHeader()
{
RestAssured
.given()
.log()
.all()
.header("Content-Type", "application/json")
.body("{\r\n" +
" \"firstname\" : \"Jim\",\r\n" +
" \"lastname\" : \"Brown\",\r\n" +
" \"totalprice\" : 111,\r\n" +
" \"depositpaid\" : true,\r\n" +
" \"bookingdates\" : {\r\n" +
" \"checkin\" : \"2018-01-01\",\r\n" +
" \"checkout\" : \"2019-01-01\"\r\n" +
" },\r\n" +
" \"additionalneeds\" : \"Breakfast\"\r\n" +
"}")
.post("https://restful-booker.herokuapp.com/booking")
.then()
.log()
.all();
}
}
Output
[RemoteTestNG] detected TestNG version 7.0.1
Request method: POST
Request URI: https://restful-booker.herokuapp.com/booking
Proxy: <none>
Request params: <none>
Query params: <none>
Form params: <none>
Path params: <none>
Headers: Accept=*/*
Content-Type=application/json; charset=UTF-8
Cookies: <none>
Multiparts: <none>
Body:
{
"firstname": "Jim",
"lastname": "Brown",
"totalprice": 111,
"depositpaid": true,
"bookingdates": {
"checkin": "2018-01-01",
"checkout": "2019-01-01"
},
"additionalneeds": "Breakfast"
}
HTTP/1.1 200 OK
Server: Cowboy
Connection: keep-alive
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 195
Etag: W/"c3-E5R+AIhAHdRg+t7MhfO18uLvLAw"
Date: Tue, 29 Sep 2020 07:52:19 GMT
Via: 1.1 vegur
{
"bookingid": 21,
"booking": {
"firstname": "Jim",
"lastname": "Brown",
"totalprice": 111,
"depositpaid": true,
"bookingdates": {
"checkin": "2018-01-01",
"checkout": "2019-01-01"
},
"additionalneeds": "Breakfast"
}
}
PASSED: WIthoutSettingContentType
===============================================
Default test
Tests run: 1, Failures: 0, Skips: 0
===============================================
===============================================
Default suite
Total tests run: 1, Passes: 1, Failures: 0, Skips: 0
===============================================
In the above approach, if you misspell the header name and its expected values then you will get an unexpected response. So to overcome that RequestSpecification class provided a below method:-
RequestSpecification contentType(ContentType contentType);
ContentType is an enum which contains members as “ANY”, “JSON”, “XML” etc. If I want to set Content -Type as JSON then I will use contentType() method as below:-
contentType(ContentType.JSON)
A complete example is as below:-
package RestAssuredBasicConcepts;
import org.testng.annotations.Test;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
public class SetContentTypeForRequest {
@Test
public void settingContentTypeAsContentType()
{
RestAssured
.given()
.log()
.all()
.contentType(ContentType.JSON)
.body("{\r\n" +
" \"firstname\" : \"Jim\",\r\n" +
" \"lastname\" : \"Brown\",\r\n" +
" \"totalprice\" : 111,\r\n" +
" \"depositpaid\" : true,\r\n" +
" \"bookingdates\" : {\r\n" +
" \"checkin\" : \"2018-01-01\",\r\n" +
" \"checkout\" : \"2019-01-01\"\r\n" +
" },\r\n" +
" \"additionalneeds\" : \"Breakfast\"\r\n" +
"}")
.post("https://restful-booker.herokuapp.com/booking")
.then()
.log()
.all();
}
}
Output
[RemoteTestNG] detected TestNG version 7.0.1
Request method: POST
Request URI: https://restful-booker.herokuapp.com/booking
Proxy: <none>
Request params: <none>
Query params: <none>
Form params: <none>
Path params: <none>
Headers: Accept=*/*
Content-Type=application/json; charset=UTF-8
Cookies: <none>
Multiparts: <none>
Body:
{
"firstname": "Jim",
"lastname": "Brown",
"totalprice": 111,
"depositpaid": true,
"bookingdates": {
"checkin": "2018-01-01",
"checkout": "2019-01-01"
},
"additionalneeds": "Breakfast"
}
HTTP/1.1 200 OK
Server: Cowboy
Connection: keep-alive
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 195
Etag: W/"c3-Hgn0HOW7t3wbXoNXHOll0aSCOGo"
Date: Tue, 29 Sep 2020 07:58:29 GMT
Via: 1.1 vegur
{
"bookingid": 12,
"booking": {
"firstname": "Jim",
"lastname": "Brown",
"totalprice": 111,
"depositpaid": true,
"bookingdates": {
"checkin": "2018-01-01",
"checkout": "2019-01-01"
},
"additionalneeds": "Breakfast"
}
}
PASSED: settingContentTypeAsContentType
===============================================
Default test
Tests run: 1, Failures: 0, Skips: 0
===============================================
===============================================
Default suite
Total tests run: 1, Passes: 1, Failures: 0, Skips: 0
===============================================
How To Retrieve And Assert Content-Type Of Response In Rest Assured
Content-Type is a header that indicates the media type or MIME( Multipurpose Internet Mail Extensions ) type or type of a file. When we hit any POST or PUT API requests we may need to pass a payload. That payload can be in any supported format by API such as XML, JSON, etc. We need to use the Content-Type header to let the server know the format of the payload for a request which will be sent.
Similarly, Content-Type for response indicates the format of the response returned. It is not mandatory that the Content-Type of request and response should be the same. We may need to assert the Content-Type of response in case of a default value.
We have already covered How To Set Content-Type For Request In Rest Assured.
Prerequisite
I have used below dependency of Rest Assured library for this post:-
<!-- https://mvnrepository.com/artifact/io.rest-assured/rest-assured -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>4.3.1</version>
<scope>test</scope>
</dependency>
Retrieving Content-Type of a response
When we call a CRUD operation using Rest Assured, it returns a reference of the Response interface. This reference holds all data related to the response of an API. We will use the same Response reference to fetch the Content-Type of a response.
Response interface extends ResponseOptions interface and ResponseOptions interface contains below two methods to retrieve the Content-Type of a response.
- String contentType(); – Get the content type of the response
- String getContentType(); – Get the content type of the response
In fact both are same difference is only the name. Second method makes more sense than first. Rest Assured provides many syntactic sugar methods. The return type of both methods is String. If returns the value of Content-Type if present otherwise NULL.
// Direct method to fetch Content-Type
String contentType = response.getContentType();
System.out.println("Content-Type of response is : "+contentType);
Since Content-Type is a header, we can extract it using methods for headers as well which we discussed here.
Example Program
package RestAssuredBasicConcepts;
import org.testng.annotations.Test;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.response.Response;
public class RetrieveAndAssertContentType {
@Test
public void retrieveAndAssertContentType()
{
Response response = RestAssured
.given()
.contentType(ContentType.JSON)
.body("{\r\n" +
" \"firstname\" : \"Jim\",\r\n" +
" \"lastname\" : \"Brown\",\r\n" +
" \"totalprice\" : 111,\r\n" +
" \"depositpaid\" : true,\r\n" +
" \"bookingdates\" : {\r\n" +
" \"checkin\" : \"2018-01-01\",\r\n" +
" \"checkout\" : \"2019-01-01\"\r\n" +
" },\r\n" +
" \"additionalneeds\" : \"Breakfast\"\r\n" +
"}")
.post("https://restful-booker.herokuapp.com/booking");
// Direct method to fetch Content-Type
String contentType = response.getContentType();
System.out.println("Content-Type of response is : "+contentType);
// Since Content-Type is an header
String contentType1 = response.getHeader("Content-Type");
System.out.println("Content-Type of response is : "+contentType1);
}
}
Output
Content-Type of response is : application/json; charset=utf-8
Content-Type of response is : application/json; charset=utf-8
Asserting Content-Type of a response without extracting
In the above section, we can assert extracted Content-Type using TestNG methods or String methods. But there is a direct way to assert it directly instead of extract and then assert. We can use method content-Type() of ValidatableResponseOptions interface. We need to use then() on Response to use ValidatableResponseOptions methods.
@Test
public void retrieveAndAssertContentType2()
{
RestAssured
.given()
.contentType(ContentType.JSON)
.body("{\r\n" +
" \"firstname\" : \"Jim\",\r\n" +
" \"lastname\" : \"Brown\",\r\n" +
" \"totalprice\" : 111,\r\n" +
" \"depositpaid\" : true,\r\n" +
" \"bookingdates\" : {\r\n" +
" \"checkin\" : \"2018-01-01\",\r\n" +
" \"checkout\" : \"2019-01-01\"\r\n" +
" },\r\n" +
" \"additionalneeds\" : \"Breakfast\"\r\n" +
"}")
.post("https://restful-booker.herokuapp.com/booking")
.then()
.contentType(ContentType.JSON);
}
ResponseSpecification – Specify How The Expected Response Must Look Like
Why ResponseSpecification?
Observe the below lines of codes carefully. You will find that we have some common statements in both @Test methods after then() method call.
package ResponseSpecificationExample;
import org.hamcrest.Matchers;
import org.testng.annotations.Test;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
public class WithoutUsingResponseSpecification {
@Test
public void getAllBookings()
{
// Given
RestAssured
.given()
.baseUri("https://restful-booker.herokuapp.com")
// When
.when()
.get("/booking")
// Then
.then()
.contentType(ContentType.JSON)
.time(Matchers.lessThan(5000L))
.statusLine("HTTP/1.1 200 OK")
// To verify booking count
.body("size()", Matchers.greaterThan(5));
}
@Test
public void getBookingDetailsWithInvalidFirstName()
{
// Given
RestAssured
.given()
.baseUri("https://restful-booker.herokuapp.com")
// When
.when()
.get("/booking?firstname=Rahul")
// Then
.then()
// Repetitive validation as first test above
.contentType(ContentType.JSON)
.time(Matchers.lessThan(5000L))
.statusLine("HTTP/1.1 200 OK")
// To verify booking count
.body("size()", Matchers.equalTo(0));
}
}
Above we have only two tests but in real-time you may have many. A group of tests will have common assertions on response. Following the DRY principle is the best way to write clean code. We have repeated common assertions in both tests which is not a good practice. If we need to modify then we need to make changes every placed used.
To club common assertions together and put as a common entity, we can use ResponseSpecification in Rest Assured. ResponseSpecification is an interface that allows you to specify how the expected response must look like in order for a test to pass. This interface has readymade methods to define assertions like status code, content type, etc. We need to use expect() method of RestAssured class to get a reference for ResponseSpecification. Remember ResponseSpecification is an interface and we can not create an object of it.
How to use ResponseSpecification?
A ResponseSpecification can be created for some assertions as below:-
// Create a ResponseSpecification
responseSpecification= RestAssured.expect();
responseSpecification.contentType(ContentType.JSON);
responseSpecification.statusCode(200);
responseSpecification.time(Matchers.lessThan(5000L));
responseSpecification.statusLine("HTTP/1.1 200 OK");
We can add a ResponseSpecification using spec() method as below:-
...
.then()
.spec(responseSpecification)
Example Program
package ResponseSpecificationExample;
import org.hamcrest.Matchers;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.specification.ResponseSpecification;
public class UsingResponseSpecification {
ResponseSpecification responseSpecification = null;
@BeforeClass
public void setupResponseSpecification()
{
// Create a ResponseSpecification
responseSpecification= RestAssured.expect();
responseSpecification.contentType(ContentType.JSON);
responseSpecification.statusCode(200);
responseSpecification.time(Matchers.lessThan(5000L));
responseSpecification.statusLine("HTTP/1.1 200 OK");
}
@Test
public void getAllBookings()
{
// Given
RestAssured
.given()
.baseUri("https://restful-booker.herokuapp.com")
// When
.when()
.get("/booking")
// Then
.then()
// Just pass ResponseSpecification as below
.spec(responseSpecification)
// To verify booking count
.body("size()", Matchers.greaterThan(5));
}
@Test
public void getBookingDetailsWithInvalidFirstName()
{
// Given
RestAssured
.given()
.baseUri("https://restful-booker.herokuapp.com")
// When
.when()
.get("/booking?firstname=jim")
// Then
.then()
.spec(responseSpecification)
// To verify booking count
.body("size()", Matchers.equalTo(0));
}
}
How To Create ResponseSpecification Using ResponseSpecBuilder
What is ResponseSpecBuilder?
ResponseSpecBuilder class provides you a builder to create a ResponseSpecification. This class has self explanatory methods like expectStatusCode(), expectHeaders() etc compare to methods of ResponseSpecification interface.
We can create a ResponseSpecification using ResponseSpecBuilder as below:-
/ Create a ResponseSpecification using ResponseSpecBuilder
ResponseSpecBuilder responseSpecBuilder = new ResponseSpecBuilder();
responseSpecBuilder.expectStatusCode(200);
responseSpecBuilder.expectStatusLine("HTTP/1.1 200 OK");
responseSpecBuilder.expectContentType(ContentType.JSON);
responseSpecBuilder.expectResponseTime(Matchers.lessThan(5000L));
ResponseSpecification responseSpecification = responseSpecBuilder.build();
Or instead of calling ResponseSpecBuilder reference multiple times, we can use the builder pattern as below:-
// Create a ResponseSpecification using ResponseSpecBuilder
responseSpecification = new ResponseSpecBuilder()
.expectStatusCode(200)
.expectStatusLine("HTTP/1.1 200 OK")
.expectContentType(ContentType.JSON)
.expectResponseTime(Matchers.lessThan(5000L))
.build();
Example Program
package ResponseSpecificationExample;
import org.hamcrest.Matchers;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import io.restassured.RestAssured;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.specification.ResponseSpecification;
public class ResponseSpecBuilderExampleBuilderPattern {
ResponseSpecification responseSpecification = null;
@BeforeClass
public void setupResponseSpecification()
{
// Create a ResponseSpecification using ResponseSpecBuilder
responseSpecification = new ResponseSpecBuilder()
.expectStatusCode(200)
.expectStatusLine("HTTP/1.1 200 OK")
.expectContentType(ContentType.JSON)
.expectResponseTime(Matchers.lessThan(5000L))
.build();
}
@Test
public void getAllBookings()
{
// Given
RestAssured
.given()
.baseUri("https://restful-booker.herokuapp.com")
// When
.when()
.get("/booking")
// Then
.then()
// Just pass ResponseSpecification as below
.spec(responseSpecification)
// To verify booking count
.body("size()", Matchers.greaterThan(5));
}
@Test
public void getBookingDetailsWithInvalidFirstName()
{
// Given
RestAssured
.given()
.baseUri("https://restful-booker.herokuapp.com")
// When
.when()
.get("/booking?firstname=jim")
// Then
.then()
.spec(responseSpecification)
// To verify booking count
.body("size()", Matchers.equalTo(0));
}
}
What Is JSON Schema?
JSON Schema
Let’s look at two images of the car below:-
When we think of a car then we visualize a structure of it. For example:- A car has four wheels, a body, fuel tank, steering, seats, etc. These are a part of the basic structure of any car. If an object which has only 3 wheels but satisfies all remaining basic structures can not be called a car.
Not all fields are mandatory while creating an account on Gmail but if you don’t give a value for mandatory fields you get an error. Similarly, a JSON that stores data as a key-value pair may have a structure that defines its allowed format, fields, values, etc. That structure can be called a JSON Schema.
A JSON Schema is a JSON document that can validate a JSON object syntactically and semantically. Syntactical validation includes if a JSON object is a valid JSON object. Semantical validations include if a valid JSON object includes all mandatory fields, the format of value, allowed values, etc.
As per the official page of JSON Schema, JSON Schema is a vocabulary that allows you to annotate and validate JSON documents. This website contains in-depth knowledge of JSON Schema.
Example JSON Schema:-
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "http://example.com/example.json",
"type": "object",
"title": "The root schema",
"description": "The root schema comprises the entire JSON document.",
"default": {},
"examples": [
{
"token": "abc123"
}
],
"required": [
"token"
],
"properties": {
"token": {
"$id": "#/properties/token",
"type": "string",
"title": "The token schema",
"description": "An explanation about the purpose of this instance.",
"default": "",
"examples": [
"abc123"
]
}
},
"additionalProperties": true
}
Above is a very simple example of JSON Schema. Focus on keys “required” and “properties“. Any JSON object against this JSON schema must have a “token” property in it and the type of value of key “token” must be a string as defined in the “properties” section. In short, whatever keys are present under “required” array, a JSON object corresponding to it must have those keys. “properties” section defines metadata about keys.
“additionalProperties” key is to represent if extra properties other than listed under “properties” section are allowed or not.
I think understanding JSON schema is quite easy. We can put validation for a property as well like min or max value or a regex.
You can use JSON Schema website to generate a JSON schema of a given JSON object and modify it as you wish.
As a tester, we must be aware of the basic concepts of JSON Schema creation. It is very useful during automation to assert if JSON request and response payload are expected or not. Not every time developer will provide you a JSON Schema. In that case, you must know how can you create JSON schema by yourselves and validate multiple things directly.
JSON Schema Validation In Rest Assured
In most of the tutorials, JSON schema validation in Rest Assured is shown for JSON response only or professionals understand that JSON schema validation can be done only for a JSON response. That is not correct. Any JSON can be validated against a related JSON schema.
Prerequisite
I have used below dependency of Rest Assured library for this post:-
<!-- https://mvnrepository.com/artifact/io.rest-assured/rest-assured -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>4.3.1</version>
<scope>test</scope>
</dependency>
If you are not aware of JSON Schema, learn about that here.
Adding maven dependency of json-schema-validator
Rest Assured provides support to JSON schema validation since 2.1.0 version. But to use this feature we need to add another Java library “json-schema-validator” in our project classpath. Since I am using a maven project I will add the below dependency in pom.xml:-
<!-- https://mvnrepository.com/artifact/io.rest-assured/json-schema-validator -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>json-schema-validator</artifactId>
<version>4.3.1</version>
</dependency>
Be careful when you search the above dependency in Maven central repo as there are multiple libraries with the same name. Make sure you look groupId as “io.rest-assured“. You can also quickly find by seeing Rest Assured official logo. If you are using non-maven project then you can download JAR file and add to classpath.
You should also add the same version of json-schema-validator as of Rest assured (4.3.1 is this case).
Class JsonSchemaValidator
JsonSchemaValidator class provides multiple overloaded static methods to perform JSON schema validation.
public static JsonSchemaValidator matchesJsonSchemaInClasspath(String pathToSchemaInClasspath) – Creates a Hamcrest matcher that validates that a JSON document conforms to the JSON schema provided to this method.
public static JsonSchemaValidator matchesJsonSchema(File file) – Creates a Hamcrest matcher that validates that a JSON document conforms to the JSON schema provided to this method.
If you keep JSON Schema files in the resource folder of your project or src/test/resources of a maven project then you can use matchesJsonSchemaInClasspath() method directly as you just need to pass the name of the JSON schema file. If you store JSON schema files at different locations within the project or outside the project then you can use the overloaded method matchesJsonSchema().
Asserting JSON response against JSON Schema
Create JSON Schema
I will use the same JSON Schema shown in this post. This JSON schema is generated for Restful Booker – Auth API. Let’s save below JSON schema under src/test/resources folder.
AuthJsonSchema.json
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "http://example.com/example.json",
"type": "object",
"title": "The root schema",
"description": "The root schema comprises the entire JSON document.",
"default": {},
"examples": [
{
"token": "abc123"
}
],
"required": [
"token"
],
"properties": {
"token": {
"$id": "#/properties/token",
"type": "string",
"title": "The token schema",
"description": "An explanation about the purpose of this instance.",
"default": "",
"examples": [
"abc123"
]
}
},
"additionalProperties": true
}
Performing JSON Schema validation using matchesJsonSchemaInClasspath()
We can directly call schema validator methods in an overloaded body(Matcher matcher) method. You may be thinking the return type of JSON Schema validator methods is JsonSchemaValidator so how can we pass them to body(Matcher matcher) method. This is because of multilevel inheritance. Class JsonSchemaValidator implements Interface Matcher indirectly.
Program
package JsonSchema;
import org.hamcrest.Matchers;
import org.testng.annotations.Test;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.module.jsv.JsonSchemaValidator;
public class VerifyJsonSchema {
@Test
public void verifyJsonSchema() {
String jsonStringPayload = "{\"username\" : \"admin\",\"password\" : \"password123\"}";
// GIVEN
RestAssured
.given()
.baseUri("https://restful-booker.herokuapp.com/auth")
.contentType(ContentType.JSON)
.body(jsonStringPayload)
// WHEN
.when()
.post()
// THEN
.then()
.assertThat()
.statusCode(200)
.body("token", Matchers.notNullValue())
.body(JsonSchemaValidator.matchesJsonSchemaInClasspath("AuthJsonSchema.json"));
}
}
The above test will pass. Let’s make some changes to the expected JSON schema to fail validation and understand the error messages.
Adding extra required properties
As of now, we get only one property “token” in response. Let’s add a new property under the “required” section of JSON schema.
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "http://example.com/example.json",
"type": "object",
"title": "The root schema",
"description": "The root schema comprises the entire JSON document.",
"default": {},
"examples": [
{
"token": "abc123"
}
],
"required": [
"token",
"nonExistingProperty"
],
"properties": {
"token": {
"$id": "#/properties/token",
"type": "string",
"title": "The token schema",
"description": "An explanation about the purpose of this instance.",
"default": "",
"examples": [
"abc123"
]
}
},
"additionalProperties": true
}
Run the above Rest Assured test and you will get console error as:-
You can see clearly that the log shows that a required field is missing.
Change the data type of properties
Property “key” holds a String value. Let’s change it to integer and observe the output.
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "http://example.com/example.json",
"type": "object",
"title": "The root schema",
"description": "The root schema comprises the entire JSON document.",
"default": {},
"examples": [
{
"token": "abc123"
}
],
"required": [
"token"
],
"properties": {
"token": {
"$id": "#/properties/token",
"type": "integer",
"title": "The token schema",
"description": "An explanation about the purpose of this instance.",
"default": "",
"examples": [
"abc123"
]
}
},
"additionalProperties": true
}
Run the same Rest Assured test again and you will get the output as below:-
You can see that it validates that a string value is found instead of an integer.
Above are just some examples of validation types. We can do a lot using this feature.
Performing JSON Schema validation using matchesJsonSchema()
Above we kept JSON schema file within src/test/resources folder and used matchesJsonSchemaInClasspath() method. We can not use this method if we do not have JSON schema in the resource folder. You will get IllegalArgumentException stating Schema to use cannot be null.
Note – I have mentioned src/test/resources to keep schema files. In fact, you can keep them in src/main/resources folder as well. Your Test classes can also be in any src/main/java or src/test/java but tests should be kept under src/test/java as a standard practice. Make sure you remove <scope> tag from maven dependency in pom.xml .
In that case, we can use another overloaded static method matchesJsonSchema() where you can pass a complete path of JSON Schema.
package JsonSchema;
import java.io.File;
import org.hamcrest.Matchers;
import org.testng.annotations.Test;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.module.jsv.JsonSchemaValidator;
public class VerifyJsonSchemaNonResource {
@Test
public void verifyJsonSchema() {
String jsonStringPayload = "{\"username\" : \"admin\",\"password\" : \"password123\"}";
// GIVEN
RestAssured
.given()
.baseUri("https://restful-booker.herokuapp.com/auth")
.contentType(ContentType.JSON)
.body(jsonStringPayload)
// WHEN
.when()
.post()
// THEN
.then()
.assertThat()
.statusCode(200)
.body("token", Matchers.notNullValue())
.body(JsonSchemaValidator.matchesJsonSchema(new File("C:\\Users\\amomahaj\\git\\master\\src\\test\\java\\JsonSchema\\schema.json")));
}
}
Remember that you need to pass the file path using a File object. Do not directly give file path as String. The method matchesJsonSchema() expects JSON schema as a string not a file path as String.
Summary
- We need to add another Java library “json-schema-validator” in our project classpath to perform JSON schema validation.
- Class JsonSchemaValidator provides static overloaded methods matchesJsonSchema() and matchesJsonSchemaInClasspath() to validate JSON schema.
- If we keep the expected JSON schema JSON file in the resource section then use matchesJsonSchemaInClasspath() method. You can use matchesJsonSchema() method as well.
- If we do not keep the expected JSON schema JSON file in the resource section then use matchesJsonSchema() method.