Gson Json Annotations Example

Google Gson provides useful annotations which can customize the serialization/deserialization of object to/from JSON. We will see commonly used Google gson annotations including @Expose, @SerializedName, @Since, @Until, & @JsonAdapter, exploring them in detail. First thing first, update project’s pom.xml to include Gson dependency as following:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.websystique.json</groupId>
  <artifactId>GsonAnnotationsExample</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>

  <name>GsonAnnotationsExample</name>

	<dependencies>
		<dependency>
			<groupId>com.google.code.gson</groupId>
			<artifactId>gson</artifactId>
			<version>2.3.1</version>
		</dependency>
	</dependencies>
</project>

Let’s start now with each annotation:

1) @Expose

It is useful in situations where you want to control if certain field should be considered for Serialization/deserialization. This annotation is applied on field level and is effective only when Gson is created with GsonBuilder and invoking excludeFieldsWithoutExposeAnnotation like below:

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

In this manner, only the field which are annotated with @Expose will be considered for serializaion/deserialization

Complete Example

Below is our sample model class with annotations on fields.

package com.websystique.json.gson;

import java.util.ArrayList;
import java.util.List;

import com.google.gson.annotations.Expose;


public class Car {

	@Expose
	private String mark;
	
	@Expose(serialize = false) 
	private int model;
	
	@Expose(serialize = false, deserialize = false) 
	private String type;
	
	private String maker;
	
	private double cost;
	
	@Expose
	private List<String> colors = new ArrayList<String>();

	public String getMark() {
		return mark;
	}

	public void setMark(String mark) {
		this.mark = mark;
	}

	public int getModel() {
		return model;
	}

	public void setModel(int model) {
		this.model = model;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public String getMaker() {
		return maker;
	}

	public void setMaker(String maker) {
		this.maker = maker;
	}

	public double getCost() {
		return cost;
	}

	public void setCost(double cost) {
		this.cost = cost;
	}

	public List<String> getColors() {
		return colors;
	}

	public void setColors(List<String> colors) {
		this.colors = colors;
	}

	@Override
	public String toString() {
		return "Car [mark=" + mark + ", model=" + model + ", type=" + type
				+ ", maker=" + maker + ", cost=" + cost + ", colors=" + colors
				+ "]";
	}

}

And a main to run this example:

package com.websystique.json.gson;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class GsonExposeAnnotationExample {

	public static void main(String args[]){
		Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
		
		Car car = new Car();
		car.setMark("AUDI");
		car.setModel(2014);
		car.setType("DIESEL");
		car.setMaker("AUDI Germany");
		car.setCost(45000);
		car.getColors().add("GREY");
		car.getColors().add("BLACK");
		car.getColors().add("WHITE");

		/*Serialize */
		String jsonString = gson.toJson(car); 
		System.out.println("Serialized jsonString : "+ jsonString);
	
		
		
		/*Deserialize*/
		String inputJson  = "{\"mark\":\"AUDI\",\"model\":2014,\"type\":\"PETROL\",\"maker\":\"AUDI Germany\", \"cost\":30000,\"colors\":[\"GRAY\",\"BLACK\",\"WHITE\"]}";
		car = gson.fromJson(inputJson, Car.class);
		System.out.println("Deserialized Car : "+ car);

	}
}

Follwing is the output from above main exection:

Serialized jsonString : {"mark":"AUDI","colors":["GREY","BLACK","WHITE"]}
Deserialized Car : Car [mark=AUDI, model=2014, type=null, maker=null, cost=0.0, colors=[GRAY, BLACK, WHITE]]

You can see that the field which were not annotated with @Expose (maker,cost) are excluded from serialization and deserialzation. Additionally, the model field is excluded from Serialization as serialize is set to false in @Expose. Similarly, type field is excluded from both serialization and deserialzation as both serialize & deserialize is set to false in @Expose.

2) @SerializedName

This annotation is useful in situation where you want to serialize a field with a different name than the actual field name. Just provide the expected serialized name as annotation attribute, and Gson will make sure to read/write the field with provided name.

Complete Example

Below is our sample model class with annotations on fields.

package com.websystique.json.gson;

import java.util.ArrayList;
import java.util.List;

import com.google.gson.annotations.SerializedName;


public class Car2 {

	private String mark;

	@SerializedName("Crafted-By") 
	private String maker;
	
	private List<String> colors = new ArrayList<String>();

	public String getMark() {
		return mark;
	}

	public void setMark(String mark) {
		this.mark = mark;
	}

	public String getMaker() {
		return maker;
	}

	public void setMaker(String maker) {
		this.maker = maker;
	}

	public List<String> getColors() {
		return colors;
	}

	public void setColors(List<String> colors) {
		this.colors = colors;
	}

	@Override
	public String toString() {
		return "Car2 [mark=" + mark + ", maker=" + maker + ", colors=" + colors
				+ "]";
	}

	
}

And a main to run this example:

package com.websystique.json.gson;

import com.google.gson.Gson;

public class GsonSerializedNameAnnotationExample {

	public static void main(String args[]) {
		Gson gson = new Gson();
		Car2 car = new Car2();
		car.setMark("AUDI");
		car.setMaker("AUDI Germany");
		car.getColors().add("GREY");
		car.getColors().add("BLACK");
		car.getColors().add("WHITE");

		/* Serialize */
		String jsonString = gson.toJson(car);
		System.out.println("Serialized jsonString : " + jsonString);

		/* Deserialize */
		String inputJson = "{\"mark\":\"AUDI\",\"maker\":\"AUDI Germany\",\"colors\":[\"GRAY\",\"BLACK\",\"WHITE\"]}";
		car = gson.fromJson(inputJson, Car2.class);
		System.out.println("Deserialized Car : " + car);

		inputJson = "{\"mark\":\"AUDI\",\"Crafted-By\":\"AUDI Germany\",\"colors\":[\"GRAY\",\"BLACK\",\"WHITE\"]}";
		car = gson.fromJson(inputJson, Car2.class);
		System.out.println("Deserialized Car : " + car);
	}
}

Serialized jsonString : {"mark":"AUDI","Crafted-By":"AUDI Germany","colors":["GREY","BLACK","WHITE"]}
Deserialized Car : Car2 [mark=AUDI, maker=null, colors=[GRAY, BLACK, WHITE]]
Deserialized Car : Car2 [mark=AUDI, maker=AUDI Germany, colors=[GRAY, BLACK, WHITE]]

As you can see from above output that field annotated with @SerializedName (maker) is Serialized with provided name (Crafted-By). On Deserializaion side, it expect the field name to be the one provided. That’s why, maker is null in second output, while in third output, it is set correctly.

3) @Since, @ Until

These two annotations comes handy when you want to manage versioning of your Json classes. You can control if a particular field is considered for serialization/deserialization based on certain version.
@Since annotation indicates the version number since a member or a type has been present. What it meant is that this field will only be considered for Serialization/deserialization starting from certain version. Before that version, it will ignored.

@Until annotation indicates the version number until a member or a type should be present.What it meant is that this field will only be considered for Serialization/deserialization until certain version. After that version, it will ignored.

These annotations are applied on field level and are effective only when Gson is created with GsonBuilder and invoking GsonBuilder.setVersion(double) method like below:

Gson gson = new GsonBuilder().setVersion(double).create();

Complete Example

Below is our sample model class with annotations on fields.

package com.websystique.json.gson;

import java.util.ArrayList;
import java.util.List;


import com.google.gson.annotations.Since;
import com.google.gson.annotations.Until;

public class Car3 {

	@Since(2.0)
	private String mark;
	
	@Since(2.1) 
	private int model;

	@Until(1.9) 
	private String type;
	
	@Until(2.1)
	private String maker;
	
	private double cost;
	
	private List<String> colors = new ArrayList<String>();

	public String getMark() {
		return mark;
	}

	public void setMark(String mark) {
		this.mark = mark;
	}

	public int getModel() {
		return model;
	}

	public void setModel(int model) {
		this.model = model;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public String getMaker() {
		return maker;
	}

	public void setMaker(String maker) {
		this.maker = maker;
	}

	public double getCost() {
		return cost;
	}

	public void setCost(double cost) {
		this.cost = cost;
	}

	public List<String> getColors() {
		return colors;
	}

	public void setColors(List<String> colors) {
		this.colors = colors;
	}

	@Override
	public String toString() {
		return "Car3 [mark=" + mark + ", model=" + model + ", type=" + type
				+ ", maker=" + maker + ", cost=" + cost + ", colors=" + colors
				+ "]";
	}


}

And a main to run this example:

package com.websystique.json.gson;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class GsonSinceUntilAnnotationsExample {

	public static void main(String args[]) {
		Gson gson = new GsonBuilder().setVersion(2.0).create();
		Car3 car = new Car3();
		car.setMark("AUDI");
		car.setModel(2014);
		car.setType("DIESEL");
		car.setMaker("AUDI GERMANY");
		car.setCost(55000);
		
		car.getColors().add("GREY");
		car.getColors().add("BLACK");
		car.getColors().add("WHITE");

		/* Serialize */
		String jsonString = gson.toJson(car);
		System.out.println("Serialized jsonString : " + jsonString);

		/* Deserialize */
		String inputJson = "{\"mark\":\"AUDI\",\"model\":2014,\"type\":\"DIESEL\",\"maker\":\"AUDI Germany\",\"cost\":55000,\"colors\":[\"GRAY\",\"BLACK\",\"WHITE\"]}";
		car = gson.fromJson(inputJson, Car3.class);
		System.out.println("Deserialized Car : " + car);
	}
}

Following is the output from above main execution:

Serialized jsonString : {"mark":"AUDI","maker":"AUDI GERMANY","cost":55000.0,"colors":["GREY","BLACK","WHITE"]}
Deserialized Car : Car3 [mark=AUDI, model=0, type=null, maker=AUDI Germany, cost=55000.0, colors=[GRAY, BLACK, WHITE]]

As you can see from above output that since the version specified during Gson creation was 2.0, only the field which were complying with version were considered. Field model is starting from 2.1 while field type is valid until 1.9. So there two field are not considered for serailization/deserialization. Also note that the fields cost & colors was not annotated with version specific information, so they are version neutral and are considered for serailization/deserialization.

4) @JsonAdapter

This annotation can be used at field or class level to specify the Gson TypeAdapter to be used while serializing/deserializing. By default Gson converts application classes to JSON using its built-in type adapters but you can override this behavior by providing custom type adapters.

Below is a custom type adapter which extends TypeAdapter class and overrides read and write methods. These read and write methods are the one which will be used during serialization and deserialization of the class/field which is configured to use this custom Type adapter.

These read and write methods are based on token based approach. During deserialization/read, we read individual token iterating over them one by one using JsonReader’s hasNext & nextName methods, checking their type and getting type based value using getXXX methods. We also take special care of opening and closing brackets using beginObject/endObject & beginArray/endArray methods. Similarly, during Serialization/write, we use JsonWriter’s name & value methods.

In our case, we are simply adding 21% VAT to car cost while writing, and removing 21% VAT while reading.

package com.websystique.json.gson;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

public class CustomTypeAdapter extends TypeAdapter<Car4> {

	@Override
	public void write(JsonWriter writer, Car4 car) throws IOException {
		writer.beginObject();

		writer.name("mark").value(car.getMark());
		writer.name("model").value(car.getModel());
		writer.name("type").value(car.getType());
		writer.name("maker").value(car.getMaker());

		double costIncludingVAT = car.getCost() + 0.21 * car.getCost();// Add 21% VAT
		writer.name("cost").value(costIncludingVAT);

		writer.name("colors");
		writer.beginArray();
		for (String color : car.getColors()) {
			writer.value(color);
		}
		writer.endArray();
		writer.endObject();
		writer.close();
	}

	@Override
	public Car4 read(JsonReader reader) throws IOException {

		Car4 car = new Car4();
		reader.beginObject();
		while (reader.hasNext()) {
			String name = reader.nextName();
			if (name.equals("mark")) {
				car.setMark(reader.nextString());
			} else if (name.equals("model")) {
				car.setModel(reader.nextInt());
			} else if (name.equals("type")) {
				car.setType(reader.nextString());
			} else if (name.equals("maker")) {
				car.setType(reader.nextString());
			} else if (name.equals("cost")) {
				double cost = reader.nextDouble();
				double costExcludingVAT = cost / 1.21;
				car.setCost(costExcludingVAT);	//Remove VAT 21%
			} else if (name.equals("colors") && reader.peek() != JsonToken.NULL) {
				car.setColors(readStringArray(reader));
			} else {
				reader.skipValue();
			}
		}
		reader.endObject();
		return car;
	}

	public List<String> readStringArray(JsonReader reader) throws IOException {
		List<String> colors = new ArrayList<String>();

		reader.beginArray();
		while (reader.hasNext()) {
			colors.add(reader.nextString());
		}
		reader.endArray();
		return colors;
	}
}

Now, let’s annotate our model car class with @JsonAdapter specifying the custom adapter we created above. Note that @JsonAdapter annotation can be used on both class and field level. Here we are only depicting class level usage.

package com.websystique.json.gson;

import java.util.ArrayList;
import java.util.List;

import com.google.gson.annotations.JsonAdapter;

@JsonAdapter(CustomTypeAdapter.class)
public class Car4 {

	private String mark;

	private int model;

	private String type;

	private String maker;

	private double cost;

	private List<String> colors = new ArrayList<String>();

	public String getMark() {
		return mark;
	}

	public void setMark(String mark) {
		this.mark = mark;
	}

	public int getModel() {
		return model;
	}

	public void setModel(int model) {
		this.model = model;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public String getMaker() {
		return maker;
	}

	public void setMaker(String maker) {
		this.maker = maker;
	}

	public double getCost() {
		return cost;
	}

	public void setCost(double cost) {
		this.cost = cost;
	}

	public List<String> getColors() {
		return colors;
	}

	public void setColors(List<String> colors) {
		this.colors = colors;
	}

	@Override
	public String toString() {
		return "Car4 [mark=" + mark + ", model=" + model + ", type=" + type
				+ ", maker=" + maker + ", cost=" + cost + ", colors=" + colors
				+ "]";
	}

}

NOTE : : Alternatively, you can choose not to use this annotation @JsonAdapter and simply register the custom type adapter we made above with GsonBuilder like below.

Gson gson = new GsonBuilder().registerTypeAdapter(Car4.class, new CustomTypeAdapter()).create();

You will get same behavior. In our example, we will use annotation.

Main to run this example:

package com.websystique.json.gson;

import com.google.gson.Gson;

public class GsonJsonAdapterAnnotationExample {

	public static void main(String args[]) {
		Gson gson = new Gson();
		Car4 car = new Car4();
		car.setMark("AUDI");
		car.setModel(2014);
		car.setType("DIESEL");
		car.setMaker("AUDI GERMANY");
		car.setCost(55000);
		
		car.getColors().add("GREY");
		car.getColors().add("BLACK");
		car.getColors().add("WHITE");

		/* Serialize */
		String jsonString = gson.toJson(car);
		System.out.println("Serialized jsonString : " + jsonString);

		/* Deserialize */
		String inputJson = "{\"mark\":\"AUDI\",\"model\":2014,\"type\":\"DIESEL\",\"maker\":\"AUDI Germany\",\"cost\":66550,\"colors\":[\"GRAY\",\"BLACK\",\"WHITE\"]}";
		car = gson.fromJson(inputJson, Car4.class);
		System.out.println("Deserialized Car : " + car);
	}
}

Run above main, you get following output:

Serialized jsonString : {"mark":"AUDI,Transporter likes it","model":2014,"type":"DIESEL","maker":"AUDI GERMANY","cost":66550.0,"colors":["GREY","BLACK","WHITE"]}
Deserialized Car : Car4 [mark=AUDI, model=2014, type=AUDI Germany, maker=null, cost=55000.0, colors=[GRAY, BLACK, WHITE]]

You can see that while writing/serializing, cost of car was included with 21% VAT. Similarly, while reading/deserializing, cost of car was diminished by 21% VAT. You can apply any custom behavior using custom type adapters.

References