Showing posts with label json. Show all posts
Showing posts with label json. Show all posts

Wednesday, April 3, 2019

Part II : Jackson Mix-ins, De/serializing third party objects with better control


Hello Friends, Welcome to my blog. Today in this blog we will be talking on Jackson Mix-ins feature, which allows us to have better control over the serialization of third party classes in which we cannot add our Jackson annotations.

Let's look at below example, Here we are using the Exception class from the java.lang package and we wanted to use this class to represent error object in our application. We will be storing this in our database.

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

  Exception ex = new Exception("Unable to Perform Operations");
  try {
   String jsonRep = mapper.writeValueAsString(ex);
   System.out.println("Third Party Object Representation : ");
   System.out.println(jsonRep);
  } catch (JsonProcessingException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }
}
Output :
Third Party Object Representation : 
{
  "cause" : null,
  "stackTrace" : [ {
    "methodName" : "main",
    "fileName" : "JacksonMixInDemo.java",
    "lineNumber" : 16,
    "className" : "blog.javahotfix.jacksondemo.application.JacksonMixInDemo",
    "nativeMethod" : false
  } ],
  "localizedMessage" : "Unable to Perform Operations",
  "message" : "Unable to Perform Operations",
  "suppressed" : [ ]
}


If you observe the output of the above program, you might see the stack trace & suppressed attribute that you don't want in your serialized JSON representation.

How will you do it?

Once Option could be write wrapper around the Exception class or write a custom parser which will take out unwanted fields. But this will be the overhead to maintain those code. So in this situation, we can use the Jackson mix-in feature, which allows us to control the visibility of the fields in serialized JSON representation.

Let's take a look at below example which shows how can we ignore the unwanted filed from JSON using Jackson mix-in feature.

  1. Create the abstract class or interface which is known as a mix-in class 
  2. Add the Getter for the fields which you don't want in your JSON representation
  3. Add JSON Ignore annotation over them
  4. Configure the ObjectMapper instance to know for which third-party class we have a mix-in class in our code base. This can be done using addMixIn Method by passing Source class & Mix-in class.

Let's take a look at the above steps in action.
package blog.javahotfix.jacksondemo.application;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

public class JacksonMixInDemo {
 public static void main(String args[]) {
  
  ObjectMapper mapper = new ObjectMapper();
  mapper.enable(SerializationFeature.INDENT_OUTPUT);
  // You can use Abstract class or Interface eighter of them
  mapper.addMixIn(Exception.class, ExceptionMixIns.class);
 //mapper.addMixIn(Exception.class, ExceptionMixIn2.class);
  
  Exception ex = new Exception("Unable to Perform Operations");
  
  try {
   String jsonRep = mapper.writeValueAsString(ex);
   System.out.println("Third Party Object Representation : ");
   System.out.println(jsonRep);
  } catch (JsonProcessingException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }
}

abstract class ExceptionMixIns {
 @JsonIgnore
 public abstract Object getStackTrace();
 
 @JsonIgnore
 public abstract Object getSuppressed();
}

interface ExceptionMixIn2 {
 @JsonIgnore
 public abstract Object getStackTrace();
 
 @JsonIgnore
 public Object getSuppressed();
}

Output :
Third Party Object Representation : 
{
  "cause" : null,
  "localizedMessage" : "Unable to Perform Operations",
  "message" : "Unable to Perform Operations"
}

I hope you have understood this Jackson mix-in feature. You can have much cleaner code with using this feature. And the code will be easily portable to another JSON processing library.


That's all for this blog friends if you have any queries, concern or feedback please let us know in comment box below till then

Good Bye !!! Happy Coding !!!

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 ! ! ! !