Friday, March 29, 2019

Part I : Understanding basics of Fasterxml Jackson


Hello, friends today in this blog we will be learning about the JSON processing in java using "Fasterxml Jackson Library". This library can handle many JSON processing operation like add a node, update the existing node, delete the node from JSON. Serialize java object to JSON and Deserialize back to Java Objects.

We will be covering below things in this tutorial
  1. Add, Update, Delete Node from JSON
  2. Serialize java object to JSON
  3. Deserialize to java object from JSON
  4. Deserialize to java object when there is no default constructor
  5. Ignoring fields completely while De/serialization
  6. Ignoring fields if it is null while serialization
  7. Ignoring unknown fields in JSON

Before we start you need to import below dependency in your project. am using Gradle for dependency management.
dependencies {
   compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.8'
   compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.9.8'
   compile group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.9.8'  
}


1. Add, Update Delete Node from JSON
Let's look at below example of List of Students with their age and grade. We will be performing the Add, Update and Delete operation on the JSON.
// This is our JSON on which we are going to perform operation
String jsonRep = "{ \"studentList\" : [" +
"{\"name\" : \"robort\", \"age\" : 9, \"grade\" :  \"A+\"}," +
"{\"name\" : \"mili\", \"age\" : 10, \"grade\" :  \"A\" }," +
"{\"name\" : \"jack\", \"age\" : 10, \"grade\" :  \"B+\"}]}";

// Create ObjectMapper object
ObjectMapper mapper = new ObjectMapper();
ObjectReader reader = mapper.reader();

// Represent JSON Element or JSON Array
JsonNode node = reader.readTree(jsonRep);

// For JSON Arrays use ArrayNode class
ArrayNode list = (ArrayNode) node.get("studentList");

// For JSON Element use Object Node class
ObjectNode newNode = mapper.createObjectNode();

// Add properties of JSON Element
newNode.put("name", "test");
newNode.put("age", 11);
newNode.put("grade", "A");
list.add(newNode);

System.out.println("New Node Added.");
System.out.println(node);

ObjectNode robortNode = (ObjectNode) list.get(0);
// Update existing property for the JSON element
robortNode.put("age", 10);

System.out.println("Robort Age is update");
System.out.println(node);

list.remove(1);
System.out.println("Mili's record removed");
System.out.println(node);

Output:-
New Node Added.
{"studentList":[{"name":"robort","age":9,"grade":"A+"},{"name":"mili","age":10,"grade":"A"},{"name":"jack","age":10,"grade":"B+"},{"name":"test","age":11,"grade":"A"}]}
Robort Age is update
{"studentList":[{"name":"robort","age":10,"grade":"A+"},{"name":"mili","age":10,"grade":"A"},{"name":"jack","age":10,"grade":"B+"},{"name":"test","age":11,"grade":"A"}]}
Mili's record removed
{"studentList":[{"name":"robort","age":10,"grade":"A+"},{"name":"jack","age":10,"grade":"B+"},{"name":"test","age":11,"grade":"A"}]}


2. Serialize java object to JSON
This is the most common scenario when we need to serialize the Java object to JSON so that it can be transferred over HTTP or plain text-based protocol and from the other end re-assembled to the java object.
// This is employee object which we need to serialize to JSON
Employee e1 = new Employee();
e1.setId(1);
e1.setName("Mr. Robort bond");
e1.setCity("Pune");
e1.setDesignation("Manager");

// Creating ObjectMapper instance
ObjectMapper mapper = new ObjectMapper();

// Enable indented output of JSON
mapper.enable(SerializationFeature.INDENT_OUTPUT);

String jsonString;
try {
 jsonString = mapper.writeValueAsString(e1);
 System.out.println(jsonString);
} catch (JsonProcessingException e) {
 e.printStackTrace();
 System.out.println("Could Not Prepare JSON String");
}

Output:
{
  "id" : 1,
  "name" : "Mr. Robort bond",
  "designation" : "Manager",
  "city" : "Pune"
}


3. Deserialize to java object from JSON
Here we are constructing the Java object from JSON. This is the plain scenario like your java class is plain POJO class having a default constructor. We will see some more complicated example the in next example.
// Here is our source JSON
String json = "{ \"id\" : 1, \"name\" : \"Mr. Robort bond\", "+
 " \"designation\" : \"Manager\", \"city\" : \"Pune\"}";
ObjectMapper mapper = new ObjectMapper();
try {
 // We will be using readValue method to parse the JSON to java object
 Employee emp = mapper.readValue(json, Employee.class);
 System.out.println("Employee Object :");
 System.out.println(emp.toString());
} catch (IOException e) {
 e.printStackTrace();
 System.out.println("Could Not Construct Object");
}

Output :
Employee Object :
Employee [id=1, name=Mr. Robort bond, designation=Manager, city=Pune]


4. Deserialize to java object when there is no default constructor
There are some specific conditions when we don't have a default constructor to the class, instead of that we have public parameterized constructor. In some condition, we have a private default constructor and we have a static factory method which will create the of the class for you. In such cases, Jackson will not able to create the instance of your class and it will throw below error

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `blog.javahotfix.jacksondemo.entity.MyFile` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: XXXXX . . . .

So in the below code snippet, we will be covering these kinds of example.
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class MyFile {
 private String name;
 private String path;
 private int size;
 
 @JsonCreator
 public MyFile(
   @JsonProperty("name") String name, 
   @JsonProperty("path") String path,
   @JsonProperty("size") int size) {
  
  this.name = name;
  this.path = path;
  this.size = size;
 }
 
 /* GETTER AND SETTERS FOR THE FIELDS */

 @Override
 public String toString() {
  return "MyFile [name=" + name + ", path=" + path + 
   ", size=" + size + "]";
 }
}

public class MyFileV2 {
 private String name;
 private String path;
 private int size;
 
 @JsonCreator
 public static MyFileV2 createInstance(
   @JsonProperty("name") String name,
   @JsonProperty("path") String path, 
   @JsonProperty("size") int size) {
  MyFileV2 obj = new MyFileV2(name, path, size);
  return obj;
 }
 
 private MyFileV2(String name, String path, int size) {
  super();
  this.name = name;
  this.path = path;
  this.size = size;
 }

 /* GETTER AND SETTERS FOR THE FIELDS */

 @Override
 public String toString() {
  return "MyFile [name=" + name + ", path=" + path + ", size=" + size
    + "]";
 }
}


public class JsonToJava2 {
 public static void main(String args[]) {
  
  String json = 
     "{ \"name\" : \"myfile1.txt\", " +
     " \"path\" : \"/install/javahotfix/files\", " +
     " \"size\" : 1024 }";
  
  ObjectMapper mapper = new ObjectMapper();
  
  try {
   MyFile file = mapper.readValue(json, MyFile.class);
   System.out.println("My File : ");
   System.out.println(file);

   MyFileV2 file2 = mapper.readValue(json, MyFileV2.class);
   System.out.println("My File v2 :");
   System.out.println(file2);
  } catch (IOException e) {
   System.out.println("Unable to Parse JSON");
   e.printStackTrace();
  }
  
 }
}
Output :
My File : 
MyFile [name=myfile1.txt, path=/install/javahotfix/files, size=1024]
My File v2 :
MyFile [name=myfile1.txt, path=/install/javahotfix/files, size=1024]


5. Ignoring fields completely while De/serialization
There are many cases where our representation class contains some internal processing flags or data that we don't want to pass through the serialization or don't want to take into the system through deserialization. To ignore such fields we can use the @JsonIgnore annotation provided by the Jackson. This annotation will tell to the Jackson to skip the field during De/serialization. Let's look at the below example.

import com.fasterxml.jackson.annotation.JsonIgnore;

public class Fruit {
 private int id;
 private String name;
 private double ratePerKg;
 
 // some internal processing parameters
 @JsonIgnore
 private int storageRackNumber;
 
 @JsonIgnore
 private boolean readyToSell;
 
 /* GETTER AND SETTERS */
 
 @Override
 public String toString() {
  return "Fruit [id=" + id + ", name=" + name + 
   ", ratePerKg=" + ratePerKg + 
   ", storageRackNumber=" + storageRackNumber + 
   ", readyToSell=" + readyToSell + "]";
 }
}

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

public class JsonIgnoreField {
public static void main(String args[]) { 
 Fruit apple = new Fruit();
 apple.setId(1);
 apple.setName("Apple");
 apple.setRatePerKg(120.00);
 apple.setStorageRackNumber(24);
 apple.setReadyToSell(false);
 
 ObjectMapper mapper = new ObjectMapper();
 mapper.enable(SerializationFeature.INDENT_OUTPUT);
 
 try {
  String jsonRep = mapper.writeValueAsString(apple);
  System.out.println("Apple instance :");
  System.out.println(jsonRep);
 } catch (JsonProcessingException e) {
  System.out.println("Unable to parse the object to JSON");
  e.printStackTrace();
 }
 
 String json = "{ \"id\" : 1, \"name\" : \"Mango\", " +
 " \"ratePerKg\" : 160.0 ,\"storageRackNumber\" : 22 }";

 try {
  Fruit mango = mapper.readValue(json, Fruit.class);
  System.out.println("Mango Instance :");
  System.out.println(mango);
 } catch (IOException e) {
  System.out.println("Unable to parse the object to JSON");
  e.printStackTrace();
 }
}
}

Output :
Apple instance :
{
  "id" : 1,
  "name" : "Apple",
  "ratePerKg" : 120.0
}
Mango Instance :
Fruit [id=1, name=Mango, ratePerKg=160.0, storageRackNumber=0, readyToSell=false]


6. Ignoring fields if it is null while serialization
When we serialize java object, by default it put all the fields to JSON whether the values are null or not. If we want to exclude the fields from serialization if they are null, then we can enable this feature by adding annotation over source class to include only not null values. let's look at the below example
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

@JsonInclude(value=Include.NON_NULL)
public class Fruit {
 private int id;
 private String name;
 private double ratePerKg;
 private List seasons;
 
 /* GETTERS AND SETTERS */
}

import java.io.IOException;
import java.util.Arrays;

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

import blog.javahotfix.jacksondemo.entity.Fruit;

public class JsonIgnoreField {
 public static void main(String args[]) {
  
  Fruit apple = new Fruit();
  apple.setId(1);
  apple.setName("Apple");
  apple.setRatePerKg(120.00);
  apple.setSeasons(Arrays.asList("Winter", "Summer"));
  
  Fruit mango = new Fruit();
  mango.setId(2);
  mango.setName("Mango");
  mango.setRatePerKg(160.00);
  
  ObjectMapper mapper = new ObjectMapper();
  mapper.enable(SerializationFeature.INDENT_OUTPUT);
  
  try {
   String jsonRepApple = mapper.writeValueAsString(apple);
   System.out.println("Apple instance :");
   System.out.println(jsonRepApple);
   
   String jsonRepMango = mapper.writeValueAsString(mango);
   System.out.println("Mango instance :");
   System.out.println(jsonRepMango);
  } catch (JsonProcessingException e) {
   System.out.println("Unable to parse the object to JSON");
   e.printStackTrace();
  }
 }
}


Output :
Apple instance :
{
  "id" : 1,
  "name" : "Apple",
  "ratePerKg" : 120.0,
  "seasons" : [ "Winter", "Summer" ]
}
Mango instance :
{
  "id" : 2,
  "name" : "Mango",
  "ratePerKg" : 160.0
}


7. Ignoring unknown fields in JSON
If you are Deserializing the JSON to java object & if the unknown properties come through the JSON, then the Deserialization fail. This is the default nature of Jackson.

com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "unknownProp" (class XXX.XXX), not marked as ignorable (2 known properties: "id", "name"])  at [ Source: (String)"{ "id" : 1, "name" : "Mango" , "unknownProp" : "bla bla" }"; line: 1, column: 49] (through reference chain:...... 

If you want to configure the things like it will simply ignore unknown property then you can configure like in the below example.

public class Fruit {
 private int id;
 private String name;
 
 /* GETTERS AND SETTERS*/ 

 @Override
 public String toString() {
  return "Fruit [id=" + id + ", name=" + name + "]";
 }
}

import blog.javahotfix.jacksondemo.entity.Fruit;
import java.io.IOException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JsonIgnoreField {
 public static void main(String args[]) {
  
  String json = "{ \"id\" : 1, " + 
    " \"name\" : \"Mango\" , " +
    " \"unknownProp\" : \"bla bla\" }";
  
  ObjectMapper mapper = new ObjectMapper();
  mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
  
  try {
   Fruit mango = mapper.readValue(json, Fruit.class);
   System.out.println("Mango Object \n" + mango);
  } catch (IOException e) {
   System.out.println("Unable to parse the object to JSON");
   e.printStackTrace();
  }
 }
}


Output :
Mango Object 
Fruit [id=1, name=Mango]


Well, we come to the end of this blog, Jackson is a very big topic but to get started with it, this blog will help you. I will be writing more blogs on advance Jackon concept. Stay tuned ! ! !

Till then
Good Bye and Happy Coding ! ! ! !

0 comments:

Post a Comment