Serialize Java object to JSON and back
There are many libraries in Java that support serialization of Java objects to JSON and back. Below are few of them.
Jackson is a very popular JSON processor and is widely used. This tutorial will demonstrate how to serialize Java object to JSON and de-serialize it back using Jackson 2.x library.
In this example, we have an Employee class that contains some mixed type of properties namely integer, String and custom Compensation class type. We will serialize the Employee object to JSON and back. We will show how the serialized object looks like. And then, we will demonstrate how to control the serialization process with custom serializers and de-serializers.
First, take a look at the Employee class that will be serialized.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
// type to be serialized public class Employee { private int id; private String name; private int age; private String designation; private Compensation compensation; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getDesignation() { return designation; } public void setDesignation(String designation) { this.designation = designation; } public Compensation getCompensation() { return this.compensation; } public void setCompensation(Compensation compensation) { this.compensation = compensation; } @Override public String toString() { return String .format("Employee: [id: %s, name: %s, age: %s, designation: %s, compensation: %s ]", id, name, age, designation, compensation); } } |
Below is the Compensation class that the Employee class internally refers to.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Compensation { private char currency; private long salary; public void setCurrency(char currency) { this.currency = currency; } public void setSalary(long salary) { this.salary = salary; } public char getCurrency() { return this.currency; } public long getSalary() { return this.salary; } @Override public String toString() { return String.format("%s %s", currency, salary); } } |
Object to JSON
Below is the main class that serializes the Employee object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import java.io.IOException; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; public class SerializationExample { public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException { // create the mapper ObjectMapper mapper = new ObjectMapper(); // enable pretty printing mapper.enable(SerializationFeature.INDENT_OUTPUT); // serialize the object mapper.writeValue(System.out, getEmployee()); } static Employee getEmployee() { Employee employee = new Employee(); employee.setId(1001); employee.setName("Drona"); employee.setAge(25); employee.setDesignation("Manager"); Compensation compensation = new Compensation(); compensation.setCurrency('₹'); compensation.setSalary(30000); employee.setCompensation(compensation); return employee; } } |
First, we create the Jackson ObjectMapper instance. ObjectMapper is the data binder that provides functionality for converting between Java objects and matching JSON constructs. Then, we call the writeValue() method on the binder passing in the OutputStream where the object will be serialized and the object itself, that is to be serialized. The writeValue() is overloaded to take File or Writer types to which the input object can be serialized.
Output
1 2 3 4 5 6 7 8 9 10 11 |
{ "id" : 1001, "name" : "Drona", "age" : 25, "designation" : "Manager", "compensation" : { "currency" : "₹", "salary" : 30000 } } |
JSON to Object
To de-serialize, we call the readValue() method of the ObjectMapper passing in the JSON source and the class type to whose instance the JSON would be deserialized to. The readValue() method is overloaded to accept other JSON input sources such as byte[], String, URL, File, InputStream etc.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import java.io.IOException; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; public class DeserializationExample { public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException { // create the mapper ObjectMapper mapper = new ObjectMapper(); // de-serialize JSON to object Employee employee = mapper.readValue(getEmployeeJson(), Employee.class); // print the de-serialized object System.out.println(employee); } static String getEmployeeJson() { return "{ " + " \"id\" : 1001, " + " \"name\" : \"Drona\", " + " \"age\" : 25, " + " \"designation\" : \"Manager\", " + " \"compensation\" : { " + " \"currency\" : \"₹\", " + " \"salary\" : 30000 " + " } " + "} "; } } |
Output
1 2 |
Employee: [id: 1001, name: Drona, age: 25, designation: Manager, compensation: ₹ 30000 ] |
Custom Serializers and De-serializers
The default serialization is desirable in most cases but sometimes you may need more control on the way the objects are serialized. For eg. In the serialization example above, you may want the Compensation object to be serialized as a string and not as an object in the JSON.
Also, when this string is being de-serialized it should be correctly interpreted and mapped to the correct class instance. To handle such customizations, Jackson allows you to write custom serializers and de-serializers.
Writing custom serializers and de-serializers is quite simple. To write a custom serializer, all you need to do is extend the generic class com.fasterxml.jackson.databind.JsonSerializer by specifying the type to be serialized as the type parameter and implement the serialize() method. The serialize() method accepts the type parameter that you specified and a JsonGenerator type.
The custom implementation of the serialize() method is supposed to extract information out of the input type parameter instance and use the JsonGenerator instance to serialize the data the way it desires. Lets write a custom serializer for the Compensation class from the earlier example and serialize it as a string instead of an object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import java.io.IOException; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; public class CompensationSerializer extends JsonSerializer<Compensation>{ @Override public void serialize(Compensation value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { jgen.writeString(value.getCurrency() + " " + value.getSalary()); } } |
Now, for the ObjectMapper to use our custom serializer, we need to create a module and add the custom serializer for the Compensation class and then register the module with ObjectMapper. Below is the code.
Custom Serializer Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
import java.io.IOException; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.module.SimpleModule; public class CustomSerializerExample { public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException { // create the mapper ObjectMapper mapper = new ObjectMapper(); // enable pretty printing mapper.enable(SerializationFeature.INDENT_OUTPUT); // create a custom serializer module SimpleModule customSerializerModule = new SimpleModule(); // add serializer for the Compensation class customSerializerModule.addSerializer(Compensation.class, new CompensationSerializer()); // register the serializer module mapper.registerModule(customSerializerModule); // serialize the object mapper.writeValue(System.out, getEmployee()); } static Employee getEmployee() { Employee employee = new Employee(); employee.setId(1001); employee.setName("Drona"); employee.setAge(25); employee.setDesignation("Manager"); Compensation compensation = new Compensation(); compensation.setCurrency('₹'); compensation.setSalary(30000); employee.setCompensation(compensation); return employee; } } |
Output
1 2 3 4 5 6 7 8 |
{ "id" : 1001, "name" : "Drona", "age" : 25, "designation" : "Manager", "compensation" : "₹ 30000" } |
Custom De-Serializer Example
Since, the compensation is serialized as a primitive type string and not as an object, you need to write a custom de-serializer for the Employee object and not the primitive type string.
While de-serializing the Employee object you need to split the compensation value into the currency character and the salary and then populate them into the Compensation object. Below is the code.
De-serializer for the Employee class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import java.io.IOException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; public class EmployeeDeserializer extends JsonDeserializer<Employee>{ @Override public Employee deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { JsonNode node = jp.getCodec().readTree(jp); Employee employee = new Employee(); employee.setId(node.get("id").asInt()); employee.setName(node.get("name").textValue()); employee.setAge(node.get("age").asInt()); employee.setDesignation(node.get("designation").textValue()); String salary = node.get("compensation").textValue(); Compensation compensation = new Compensation(); compensation.setCurrency(salary.charAt(0)); compensation.setSalary(Long.parseLong(salary.substring(2))); employee.setCompensation(compensation); return employee; } } |
Main class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import java.io.IOException; import com.fasterxml.jackson.core.JsonGenerationException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; public class CustomDeserializationExample { public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException { // create the mapper ObjectMapper mapper = new ObjectMapper(); // create a custom de-serializer module SimpleModule customDeSerializerModule = new SimpleModule(); // add de-serializer for the Employee class customDeSerializerModule.addDeserializer(Employee.class, new EmployeeDeserializer()); // register the de-serializer module mapper.registerModule(customDeSerializerModule); // serialize the object Employee employee = mapper.readValue(getEmployeeJson(), Employee.class); // print the de-serialized object System.out.println(employee); } static String getEmployeeJson() { return "{ " + " \"id\" : 1001, " + " \"name\" : \"Drona\", " + " \"age\" : 25, " + " \"designation\" : \"Manager\", " + " \"compensation\" : \"₹ 30000\" " + "} "; } } |
Output
1 2 |
Employee: [id: 1001, name: Drona, age: 25, designation: Manager, compensation: ₹ 30000 ] |
I was forced to precede writing object with
jgen.writeStartObject();
and finish it with
jgen.writeEndObject();
otherwise received exception
“Can not write a field name, expecting a value”
json-io, a Java to JSON and JSON to Java serializer, mentioned in the article, is now hosted here: https://github.com/jdereg/json-io
Thanks for the update! I will update the link accordingly.