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:
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.
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.
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.
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
If you like tutorials on this site, why not take a step further and connect me on Facebook , Google Plus & Twitter as well? I would love to hear your thoughts on these articles, it will help improve further our learning process.
In this post we will be developing a full-blown CRUD application using Spring Boot, AngularJS, Spring Data, JPA/Hibernate and MySQL,…
Spring Boot complements Spring REST support by providing default dependencies/converters out of the box. Writing RESTful services in Spring Boot…
Being able to start the application as standalone jar is great, but sometimes it might not be possible to run…
Spring framework has taken the software development industry by storm. Dependency Injection, rock solid MVC framework, Transaction management, messaging support,…
Let's secure our Spring REST API using OAuth2 this time, a simple guide showing what is required to secure a…
This post shows how an AngularJS application can consume a REST API which is secured with Basic authentication using Spring…