Jackson vs Gson: A Deep Dive
This is a tutorial about Jackson vs Gson. Specifically, we will do a quick comparison of the libraries Gson and Jackson for serialization of a Java object to its JSON representation and deserialization of JSON string back to an equivalent Java object. We will talk about the benefits of each and see which library to use when.
1. Maven Dependencies
Firstly, let’s grab the maven dependencies and add to the classpath before we start working on this tutorial.
1.1. Gson
In the following code snippet, we’ll have a look at the maven dependency for Gson.
Gson Maven Dependencies
<dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.6</version> </dependency>
The latest version of the gson library is available here.
1.2. Jackson
The following code snippet shows the maven dependency for Jackson.
Jackson Maven Dependencies
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.11.0</version> </dependency>
To get the latest version of the jackson library, click here.
2. Model Classes
We’ll be using the following entity classes for demonstrating serialization and deserialization operations with Gson & Jackson.
Employee.java
public class Employee { private int id; private String name; private Date date; private List<Task> tasks; // default constructor // parametrized constructor // getters , setters }
Task.java
public class Task { private int id; private List<String> tags; // default constructor // parametrized constructor // getters , setters }
Let’s define a method to get an instance of the Employee
class to be used throughout this tutorial.
getEmployee()
private static Employee getEmployee() { Task task1 = new Task(1, Arrays.asList("Java", "Python", "Go")); Task task2 = new Task(2, Arrays.asList("OAuth", "OIDC", "SAML")); Employee employee = new Employee(1, "Andy", Arrays.asList(task1, task2), new Date()); return employee; }
3. Serialization
Serialization is the process of converting a Java object to its JSON representation. Let’s see a serialization example using the libraries Gson and Jackson and note the differences.
3.1. Gson
Let’s begin with a simple serialization example using the Gson library.
Serialization With Gson
private static Gson gson = new GsonBuilder() .setPrettyPrinting().create(); public String entityToJson(Employee employee) { String jsonString = gson.toJson(employee); System.out.println("[GSON] Employee As JSON String: " + jsonString + "\n"); return jsonString; }
3.2. Jackson
In this section, we will use the Jackson library to demonstrate a serialization operation.
Serialization With Jackson
public String entityToJson(Employee employee) { String jsonString = null; try { jsonString = objectMapper.writerWithDefaultPrettyPrinter() .writeValueAsString(employee); System.out.println("[JACKSON] Employee As JSON String: " + jsonString + "\n"); } catch (JsonProcessingException e) { e.printStackTrace(); } return jsonString; }
Serialization Output
[GSON] Employee As JSON String: { "id": 1, "name": "Andy", "date": "Jul 4, 2020, 8:43:58 PM", "tasks": [ { "id": 1, "tags": [ "Java", "Python", "Go" ] }, { "id": 2, "tags": [ "OAuth", "OIDC", "SAML" ] } ] } [JACKSON] Employee As JSON String: { "id" : 1, "name" : "Andy", "date" : 1593875638646, "tasks" : [ { "id" : 1, "tags" : [ "Java", "Python", "Go" ] }, { "id" : 2, "tags" : [ "OAuth", "OIDC", "SAML" ] } ] }
The points to be noted from the above examples are:
- We used the
new GsonBuilder().setPrettyPrinting().create()
statement to create aGson
instance enabled with pretty printing. - In Jackson, the
objectMapper.writerWithDefaultPrettyPrinter()
statement provides anObjectWriter
for pretty printing. - The Jackson
ObjectMapper
by default serializes theDate
object as along
epoch value. Contrarily, Gson by default serializesDate
as a string.
4. Deserialization
Deserialization is the process of converting a JSON string back to its POJO instance.
We will use the JSON string output from the previous serialization example to demonstrate the following deserialization operations.
4.1. Gson
Let’s see an example to run through the standard Gson deserialization process.
Deserialization With Gson
public void jsonToEntity(String employeeJSON) { Employee employee = gson.fromJson(employeeJSON, Employee.class); System.out.println("[GSON] Employee: " + employee); }
4.2. Jackson
Next, let’s take a look at the standard behavior of the Jackson API for deserialization.
Deserialization With Jackson
public void jsonToEntity(String employeeJSON) { Employee employee = null; try { employee = objectMapper.readValue(employeeJSON, Employee.class); } catch (JsonProcessingException e) { e.printStackTrace(); } System.out.println("[JACKSON] Employee: " + employee); }
Deserialization Output
[GSON] Employee Employee [id=1, name=Andy, date=Sat Jul 04 20:47:16 IST 2020, tasks=[Task [id=1,tags=[Java, Python, Go]], Task [id=2,tags=[OAuth, OIDC, SAML]]]] [JACKSON] Employee Employee [id=1, name=Andy, date=Sat Jul 04 20:47:16 IST 2020, tasks=[Task [id=1, tags=[Java, Python, Go]], Task [id=2, tags=[OAuth, OIDC, SAML]]]]
The Deserialization operation prints exactly the same Java object for both Gson and Jackson libraries.
The points to be noted from the above examples are:
- For either of the libraries, the property names in the JSON object must correlate with the Java entity field names. If the names do not match, the behavior is as follows:
- (Gson) : The fields are evaluated to
null
. - (Jackson) : An
UnrecognizedPropertyException
is thrown.
- (Gson) : The fields are evaluated to
- As per the Javadoc of the class
GsonBuilder
, theDate
serialization & deserialization operations ignore the time-zone information. Therefore, any such time-zone information present in the JSON object shall be ignored.
5. Custom Serialization
Often, it is required to override the default behavior of a library for serialization. In this section, we’ll see how to create and use a custom JSON serializer.
5.1. Gson
Let’s define a custom serializer that modifies the name of the properties in the target JSON string. Also, we’ll alter the Date
representation by using a custom SimpleDateFormat
instance.
CustomGSONSerializer.java
public class CustomGSONSerializer implements JsonSerializer { private SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yy"); @Override public JsonElement serialize(Employee employee, Type typeOfSrc, JsonSerializationContext context) { JsonObject employeeDetails = new JsonObject(); JsonObject employeeJSONObj = new JsonObject(); employeeJSONObj.addProperty("<id>Employee</id>", employee.getId()); employeeJSONObj.addProperty("<name>Employee</name>", employee.getName()); employeeJSONObj.addProperty("<tasks>Employee</tasks>", String.join(":", employee.getTasks().get(0).getTags())); employeeJSONObj.addProperty("<date>Employee</date>", sdf.format(employee.getDate())); employeeDetails.add("employeeDetails", employeeJSONObj); return employeeDetails; } }
The next step is to register our custom serializer with the GsonBuilder
for the appropriate Type
. Also, we’ll add the configuration to disable HTML escaping and serialize the HTML characters as is.
Custom Serialization With Gson
public String customSerializer(Employee employee) { Gson customGson = gson.newBuilder().disableHtmlEscaping() .registerTypeAdapter(Employee.class, new CustomGSONSerializer()).create(); String jsonString = customGson.toJson(employee); System.out.println("[GSON] Custom Serializer: " + jsonString + "\n"); return jsonString; }
Finally, as shown above, we create the Gson
instance and invoke the usual toJson
method to start serialization.
5.2. Jackson
Next, let’s create a custom serializer for the Jackson ObjectMapper
with the same customizations as done in the CustomGSONSerializer
in the previous section.
CustomJacksonSerializer.java
public class CustomJacksonSerializer extends StdSerializer { private SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yy"); private static final long serialVersionUID = 1L; public CustomJacksonSerializer() { this(null); } public CustomJacksonSerializer(Class clazz) { super(clazz); } @Override public void serialize(Employee employee, JsonGenerator jsonGenerator, SerializerProvider serializer) throws IOException { jsonGenerator.writeStartObject(); jsonGenerator.writeObjectFieldStart("employeeDetails"); jsonGenerator.writeNumberField("<id>Employee</id>", employee.getId()); jsonGenerator.writeStringField("<name>Employee</name>", employee.getName()); jsonGenerator.writeStringField("<tasks>Employee</tasks>", String.join(":", employee.getTasks().get(0).getTags())); jsonGenerator.writeObjectField("<date>Employee</date>", sdf.format(employee.getDate())); jsonGenerator.writeEndObject(); } }
The following code describes how to register our own serializer with the ObjectMapper
and use it for JSON serialization operations.
Custom Serialization With Jackson
public String customSerializer(Employee employee) { ObjectMapper customObjMapper = new ObjectMapper(); SimpleModule simpleModule = new SimpleModule( "CustomJacksonSerializer", new Version(1, 0, 0, null, null, null)); simpleModule.addSerializer(Employee.class, new CustomJacksonSerializer()); customObjMapper.registerModule(simpleModule); String employeeJSON = null; try { employeeJSON = customObjMapper.writerWithDefaultPrettyPrinter() .writeValueAsString(employee); } catch (JsonProcessingException e) { e.printStackTrace(); } System.out.println("[JACKSON] Custom Serializer Employee: " + employeeJSON + "\n"); return employeeJSON; }
Let’s see the output of the execution of the previous two examples.
Custom Serialization Output
[GSON] Custom Serializer Employee: { "employeeDetails": { "<id>Employee</id>": 1, "<name>Employee</name>": "Andy", "<tasks>Employee</tasks>": "Java:Python:Go", "<date>Employee</date>": "04-07-20" } } [JACKSON] Custom Serializer Employee: { "employeeDetails" : { "<id>Employee</id>" : 1, "<name>Employee</name>" : "Andy", "<tasks>Employee</tasks>" : "Java:Python:Go", "<date>Employee</date>" : "04-07-20" } }
A few points to be noted from the above output are:
- New/modified property names introduced in the JSON string by using our own serializer.
Date
object is now serialized based on the customSimpleDateFormat
provided.
6. Custom Deserialization
There might be scenarios where we might also have to override the default deserialization behavior.
In this section, we will define our custom deserializers, register them with their libraries, and use them for deserialization operations.
6.1. Gson
The following class CustomGSONDeSerializer
attempts to parse a date by using a SimpleDateFormat
object. It’ll also handle the “<>” tags in the input JSON string.
Note: The input JSON string in this example is the same as the output from the custom serialization operation in the previous section.
CustomGSONDeSerializer.java
public class CustomGSONDeSerializer implements JsonDeserializer { private SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yy"); @Override public Employee deserialize(JsonElement jsonElement, Type typeOfSrc, JsonDeserializationContext context) throws JsonParseException { Employee employee = new Employee(); JsonObject jsonObject = jsonElement.getAsJsonObject() .get("employeeDetails").getAsJsonObject(); int empId = jsonObject.get("<id>Employee</id>").getAsInt(); employee.setId(empId); employee.setName(jsonObject.get("<name>Employee</name>").getAsString()); try { employee.setDate(sdf.parse(jsonObject.get( "<date>Employee</date>").getAsString())); } catch (ParseException e) { e.printStackTrace(); } return employee; } }
Next, let’s register our custom deserializer with the GsonBuilder
for the appropriate Type
.
Note: When reading an input JSON with “<>” tags, it is not required to configure the GsonBuilder
with disableHtmlEscaping()
, unlike custom serialization.
Lastly, the usual fromJson
method is invoked to begin the deserialization operation.
Custom Deserialization With Gson
public void customDeSerializer(String employeeJSON) { Gson customGson = gson.newBuilder().registerTypeAdapter (Employee.class, new CustomGSONDeSerializer()).create(); Employee employee = customGson.fromJson(employeeJSON, Employee.class); System.out.println("[GSON] Custom DeSerializer Employee: " + employee + "\n"); }
6.2. Jackson
In this section, we’ll create a custom deserializer for the ObjectMapper
to modify its standard behaviour. The customizations are similar to the ones defined in the CustomGSONDeSerializer
class.
CustomJacksonDeserializer.java
public class CustomJacksonDeserializer extends StdDeserializer { private static final long serialVersionUID = 1L; public CustomJacksonDeserializer() { this(null); } public CustomJacksonDeserializer(Class clazz) { super(clazz); } @Override public Employee deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { Employee employee = new Employee(); JsonNode jsonNode = jsonParser.getCodec().readTree(jsonParser); JsonNode empDetailsNode = jsonNode.get("employeeDetails"); int empId = empDetailsNode.get("<id>Employee</id>").asInt(); employee.setId(empId); employee.setName(empDetailsNode.get( "<name>Employee</name>").asText()); return employee; } }
The following code shows how to register the custom deserializer with the ObjectMapper
and use it for JSON deserialization operations.
Custom Deserialization With Jackson
public void customDeSerializer(String employeeJSON) { ObjectMapper customObjMapper = new ObjectMapper(); SimpleModule simpleModule = new SimpleModule("CustomJacksonDeserializer", new Version(1, 0, 0, null, null, null)); simpleModule.addDeserializer(Employee.class, new CustomJacksonDeserializer()); customObjMapper.registerModule(simpleModule); Employee employee = null; try { employee = customObjMapper.readValue(employeeJSON, Employee.class); } catch (JsonProcessingException e) { e.printStackTrace(); } System.out.println("[JACKSON] Custom DeSerializer Employee : " + employee + "\n"); }
Let’s have a look at the output of the custom deserialization operation using Gson and Jackson libraries.
Custom Deserialization Output
[GSON] Custom DeSerializer Employee: Employee [id=1, name=Andy, date=Sun Jul 05 00:00:00 IST 2020, tasks=null] [JACKSON] Custom DeSerializer Employee : Employee [id=1, name=Andy, date=null, tasks=null]
7. Annotations
The Gson library provides a limited set of annotations (@Expose, @Until, @Since, @SerializedName, @JsonAdapter)
. However, the Jackson library has extensive support for annotations.
In this section, we will discuss the @Expose
annotation from the Gson library and the @JsonIgnore
annotation from the Jackson API.
7.1. Gson
GsonBuilder
provides a configuration to exclude certain fields during serialization and deserialization operations.
In order to do so, the properties which we want to expose and not exclude should be marked with @Expose
annotation as shown below.
Product.java
public class Product { @Expose private int id; @Expose private String name; @Expose private String type; private boolean launched;
Next, we create the Gson
instance by calling the method excludeFieldsWithoutExposeAnnotation()
on the GsonBulilder
.
Also, note the use of serializeNulls()
method. This overrides the default behaviour of Gson to ignore null values during serialization.
In other words, we force Gson to serialize properties with null values.
Gson Configuration For @Expose
public void ignoreAndSerialize(Product product) { Gson customGson = gson.newBuilder() .excludeFieldsWithoutExposeAnnotation() .serializeNulls().create(); System.out.println("[GSON] Ignore And Serialize: " + customGson.toJson(product)); }
7.2. Jackson
The @JsonIgnore
annotation is similar to the @Expose
annotation. It is used to mark a property to be ignored from being serialized.
Unlike GsonBuilder
, no additional configuration is required to work with this annotation.
Product.java
public class Product { private int id; private String name; private String type; @JsonIgnore private boolean launched;
Serialization Using Annotations Ouput
[GSON] Ignore And Serialize: { "id": 1, "name": "Television", "type": "Electronic" } [JACKSON] Ignore And Serialize: { "id" : 1, "name" : "Television", "type" : "Electronic" }
Some observations from the above output are:
- When using Gson the field
launched
is not marked with@Expose
and hence is excluded from serialization. - In the case of Jackson, the
launched
property is annotated with@JsonIgnore
. Therefore, it is ignored for serialization.
8. Summary
To summarise let’s see some noticeable difference between the two libraries.
- The Gson library is designed for scenarios where you do not have access to the source code for adding annotations.
- Also, it provides extensive support for Java Generics.
- The
toJson
and thefromJson
methods from the Gson library throw either aJsonSyntaxException
or aJsonIOException
which are unchecked exceptions (a subclass ofRuntimeException
).
Contrarily:
- The Jackson API provides a rich support for annotation-based configuration.
- It is the default library for serialization and deserialization operations in the Spring Boot framework.
- The
readValue
andwriteValue
methods of theObjectMapper
class throw checked exceptions (a subclass ofIOException
andException
).
To conclude, both the libraries are quite similar and are excellent options for processing JSON objects, pretty simple to use and really well documented.
9. Download the source code
All the code examples provided in this tutorial are available in a Maven project and should be easy to import and run.
You can download the full source code of this example here: Jackson vs Gson: A Deep Dive