preface
This weekend by several technical bloggers in the same public account fastJSON article has been found vulnerability, this harm can lead to service paralysis! Fastjson has a serious Bug not long after the previous vulnerability event. At present, many projects use FastJSON to convert objects and JSON data, and need to update the version and redeploy, which can be said to be time-consuming and laborious. At the same time, it also brought me a new thinking. In the face of a large number of powerful open source libraries, we should not blindly introduce them into a project, because one unstable factor in many open source frameworks is enough to bring a project to its end. Taking advantage of the weekend, AT home to learn the excellent open source framework Gson also has JSON and object conversion function, and plan to use fastJSON projects in the future place gradually to use Gson, record the learning summary content, I hope it will be helpful to partners.
All of the code snippets covered in this article are in the following repository, interested partners are welcome to refer to:
Github.com/wrcj12138aa…
Version support:
- JDK 8
- Gson 2.8.5
- JUnit 5.5.1
- Lomok 1.18.8
Gson profile
Before introducing Gson, we can look at the description of Gson from the official wiki. What is Gson?
Gson is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to Convert a JSON string to an Equivalent Java Object.
As you can see from the description, Gson is a Java library for converting Java objects to and from STRING data in JSON format. It was initially widely used within Google on the Android platform and Java servers. After the open source in 2008, it has become another widely used open source Framework of Google. As of now *(2019.09.08)*, there are more than 1W6 stars on GitHub. The same function class library also has Jackson integrated in Spring Framework. And Ali open source fastJSON and so on.
In terms of features, Gson provides a simple fromJson/toJson API for converting Java toJson and generating compact, readable JSON string output. It also supports complex object conversion and rich custom representations. Sufficient to meet most of our JSON data processing needs in daily development.
We usually to convert between the object and JSON string called the Serialization and Deserialization (Serialization/Deserialization). The process of converting an object to a JSON string is called serialization, and the process of converting a JSON string to an object is called deserialization.
Gson basic use
The com.google.gson.Gson object is the key object of the Gson framework. The public API provides a variety of serialization and dissequencing methods.
There are two main ways to create a Gson object:
- usenewKeyword directly created:
Gson gson = new Gson()
- Built from GsonBuilder objects:
Gson gson = new GsonBuilder().create()
In general, the Gson objects created in the above two ways behave the same when serialized and when unsequentialized, but the second way builds the Gson objects allows for additional customization of behavior, such as formatting the output of the JSON string, serializing null values, and so on.
Java serialization
Serialization of simple objects
The following example shows the different effects of serializing Java objects in both ways:
public class ResultTest {
@Test
void test_serialization(a) {
Gson gson = new Gson();
Result result = new Result(200."Success".null);
String json = gson.toJson(result);
System.out.println("json is " + json);
Gson buildedGson = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
String buildedJson = buildedGson.toJson(result);
System.out.println("buildedJson is " + buildedJson);
}
class Result {
private int code;
private String message;
private Object data;
public Result(int code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data; }}}Copy the code
Running the test case, you can see the following log output in the console:
Gson objects can be seen from the results, the default behavior serialized object will be null field to ignore, and com. Google. Gson. GsonBuilder# serializeNulls method will allow Gson object serialization null field; And normal serialized JSON string is compact format, save the string memory, using com. Google. Gson. GsonBuilder# setPrettyPrinting method after the final output of JSON string format is easier to read. Of course, in addition to these two methods, GsonBuilder provides a number of apis for customizing serialization and deserialization behavior, which we’ll cover later.
JosnObject generate JSON
In addition to the above method of converting an object from a custom class toJson, you can use the JsonObject provided by the Gson framework to build a normal object, then use the toJson method to generate a JSON string, supplement the below test class to the original test class, and run it to see what it looks like
@Test
void test_jsonObject_serialization(a) {
Gson gson = new Gson();
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("code".400);
jsonObject.addProperty("message"."Parameter error");
String toJson = gson.toJson(jsonObject);
String exceptedJson = "{\"code\":400,\"message\":\" parameter error \"}";
Assertions.assertEquals(exceptedJson, toJson); //true
}
Copy the code
The JsonObject addProperty(property,value) method can only be used to add String, Number, Boolean, Character. Because the internal is to call com. Google. Gson. JsonObject# add, value encapsulation became a JsonPrimitive object, and then save the internal variables set custom LinkedTreeMap members; If you need to add other objects to a JsonObject, you need to add a JsonElement object directly using the add(String Property, JsonElement value) method. In this case, JsonElement is an abstract class. Both JsonObject and JsonPrimitive inherit from JsonElement, so we end up using another JsonObject as a property object on top of the original JsonObject:
Gson gson = new Gson();
JsonObject jsonObject = new JsonObject();
/ /...
JsonObject nestJsonObject = new JsonObject();
nestJsonObject.addProperty("username"."one");
nestJsonObject.addProperty("score".99);
jsonObject.add("data", nestJsonObject);
String toJson2 = gson.toJson(jsonObject);
System.out.println(toJson2);
/ / {" code ": 400, the" message ":" parameter error ", "data" : {" username ":" one ", "score" : 99}}
Copy the code
JSON deserialization
Deserialization of simple objects
Now let’s take a look at how to deserialize JSON to Java objects. The main method used here is com.google.gson.gson #fromJson, Its most basic use is fromJson(String JSON, Class
classOfT), which attempts to convert a JSON String into an object of the specified Class, and throws a JsonSyntaxException if the conversion fails. We can add a test case to the original code and run it to see how it works:
@Test
void test_deserialization(a) {
String json = "{\"code\":400,\"message\":\" parameter error \"}";
Result result = new Gson().fromJson(json, Result.class);
Assertions.assertEquals(400, result.code); // true
Assertions.assertEquals("Parameter error", result.message); // true
}
Copy the code
Deserialization Map
In addition to serializing JSON strings to custom Java objects, we can also convert them to Map sets. Gson provides a simple conversion to Map sets:
@Test
void test_map(a) {
String jsonString = "{'employee.name':'one','employee.salary':10}";
Gson gson = new Gson();
Map map = gson.fromJson(jsonString, Map.class);
assertEquals(2, map.size());
assertEquals("one", map.get("employee.name"));
assertEquals(Double.class, map.get("employee.name").getClass());
}
Copy the code
It is important to note that the actual type of the transformed Map object is not the usual HashMap, but the Gson custom collection LinkedTreeMap, which implements the Map interface to store key and value pairs, is optimized for adding and deleting key and value pairs, and uses the order in which they are stored as the traversal order. So what’s stored first gets traversed first. In addition, numeric data in a JSON string will be converted to Double, and true/false data will be converted to Boolean. Specific judgment basis can refer to the com. Google. Gson. Internal. Bind. ObjectTypeAdapter# read method implementation.
JSON and Array, List conversion
Convert JSON Array
The toJson method is directly used to convert JSON data, and the fromJson method specifies that the array type is converted to the array of the corresponding type.
@Test
void test_array(a) {
Gson gson = new Gson();
int[] ints = {1.2.3.4.5};
String[] strings = {"abc"."def"."ghi"};
String s = gson.toJson(ints);/ / [1, 2, 3, 4, 5]
assertEquals("[1, 2, 3, 4, 5]", s); // true
String s1 = gson.toJson(strings);// ["abc", "def", "ghi"]
assertEquals("[\"abc\",\"def\",\"ghi\"]", s1);
String[] strings1 = gson.fromJson(s1, String[].class);
assertEquals(strings.length, strings1.length); // true
assertEquals(strings[0], strings1[0]); // true
int[] ints2 = gson.fromJson("[1, 2, 3, 4, 5]".int[].class);
assertEquals(1, ints2[0]); // true
assertEquals(5, ints2[4]); // true
}
Copy the code
JSON conversion List
To convert List data to JSON data, use Gson in the same way as you would with Array data; To convert an array of JSON data into a List of a custom class, we write it as follows:
@Test
public void givenJsonString_whenIncorrectDeserializing(a) {
Gson gson = new Gson();
String inputString = "[{\"id\":1,\"name\":\"one\"},{\"id\":2,\"name\":\"two\"}]";
List<Person> outputList = gson.fromJson(inputString, List.class);
outputList.get(0).getId();
}
Copy the code
Unfortunately, running this code throws a ClassCastException, which is described as follows:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.one.learn.Person
...
Copy the code
From the above description, we know that the List element is of type LinkedTreeMap, not Person, when we deserialize it. So when you access the ID property as a Person object, you throw a ClassCastException. FromJson (String JSON, Type typeOfT); fromJson(String JSON, Type typeOfT)
@Test
public void givenJsonString_whenCorrectDeserializing_(a) {
Gson gson = new Gson();
String inputString = "[{\"id\":1,\"name\":\"one\"},{\"id\":2,\"name\":\"two\"}]";
Type type = new TypeToken<List<Person>>(){}.getType();
List<Person> outputList = gson.fromJson(inputString, type);
int id = outputList.get(0).getId();
assertEquals(1, id); // true
assertEquals("one", outputList.get(0).getName()); // true
}
Copy the code
The Type object in this method, obtained through the getType method of the TypeToken object, is the generic Type associated with the TypeToken object. Here TypeToken is a class introduced by Gson to support generics, to solve the problem that Java does not provide a generic type representation, because TypeToken’s constructor is protected and cannot be constructed directly. New TypeToken
>() {}.getType().
Advanced use of Gson
After the basic use of Gson, let’s move on to other uses of Gson.
Deserialization of generic objects
In the previous section, we briefly introduced Gson’s support for generics. Let’s use code to show how powerful it is. First, let’s adjust the Result class above to accept generics:
class Result<T> {
private int code;
private String message;
private T data;
public Result(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data; }}Copy the code
Then parse a JSON string with an embedded object into a Result<User> object. The example code is as follows:
@Test
void test_genric_object(a) {
String json = "{\" code \ ": 200, \" message \ ": \ \" operation successful ", \ "data \" : {\ "username \", \ "one \", \ "avater \" : \ "image. JPG \" " +
"}}";
Type type = new TypeToken<Result<User>>(){}.getType();
Result<User> result = new Gson().fromJson(json, type);
Assertions.assertEquals(200, result.code);
Assertions.assertEquals("one", result.data.getUsername());
Assertions.assertEquals("image.jpg", result.data.getAvater());
}
class User {
private String username;
private String avater;
public String getUsername(a) {
return username;
}
public String getAvater(a) {
returnavater; }}Copy the code
The specific generic type Result
is obtained using the TypeToken object and then passed in the fromJson method to deserialize according to the corresponding type.
Custom serialization
If we want to perform special processing on some fields of Java objects, such as hiding the serialization of some fields and formatting the data of the fields, we can implement the JsonSerializer interface to customize the serialization logic. For example, if we need to format the DateSerializer attribute, we can declare the DateSerializer class as follows:
class DateSerializer implements JsonSerializer<Date> {
SimpleDateFormat dateTime = new SimpleDateFormat("yyyy-MM-dd");
@Override
public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
return newJsonPrimitive(dateTime.format(src)); }}Copy the code
Then, before building the Gson object, use GsonBuilder to register the DateSerializer instance as follows:
Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, new DateSerializer()).create();
Copy the code
When a field of type Date is serialized, the Date will be output in yyyY-MM-DD format using the custom serialize method, as shown in the following example:
@Test
void test_dateSerializer(a) {
MyObject myObject = new MyObject(new Date(), "one");
Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, new DateSerializer()).create();
String json = gson.toJson(myObject);
String exceptedJson = "{\"date\":\"2019-09-08\",\"name\":\"one\"}";
Assertions.assertEquals(exceptedJson, json); // true
}
class MyObject {
private Date date;
private String name;
public MyObject(Date date, String name) {
this.date = date;
this.name = name;
}
public MyObject(a) {}}Copy the code
Custom deserialization
Similar to the implementation of custom serialization, if you want to customize deserialization logic, you need to implement an interface called JsonDeserializer to implement custom deserialization logic. For example, we have a JSON string with the contents {“CODE”: 400, “MESSAGE”: ResultDeserializer = ResultDeserializer = ResultDeserializer = ResultDeserializer = ResultDeserializer = ResultDeserializer = ResultDeserializer = ResultDeserializer
class ResultDeserializer implements JsonDeserializer<Result> {
@Override
public Result deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject object = json.getAsJsonObject();
Result<Object> result = new Result<>(object.getAsJsonPrimitive("CODE").getAsInt(),object.getAsJsonPrimitive("MESSAGE").getAsString(), null);
returnresult; }}Copy the code
The next step is to register ResultDeserializer instance with GsonBuilder, generate the corresponding Gson object, and deserialize the operation:
@Test
void test_resultDeserializer(a) {
//language=JSON
String json = "{\"CODE\": 400,\"MESSAGE\": \" parameter error \"}";
Gson gson = new GsonBuilder().registerTypeAdapter(Result.class, new ResultDeserializer())
.create();
Result result = gson.fromJson(json, Result.class);
Assertions.assertEquals(400, result.code); // true
Assertions.assertEquals("Parameter error", result.message); // true
}
Copy the code
Gson often uses annotations
In addition to the apis Gson provides for developers to use, there are also a number of feature annotations that can be used. Here are the most commonly used annotations in Gson.
@Expose
This annotation can only be used on fields. It is used to indicate whether the corresponding field will be exposed during serialization or deserialization. There are two properties, serialize and deserialize, which default to true. When a field is annotated as @expose (serialize = true, deserialize = false), it indicates that the field will only be visible during serialization and that the assignment will be ignored during deserialization. Need extra attention is that @ Expose annotations only when constructing Gson GsonBuilder way is limited, and must be called before build excludeFieldsWithoutExposeAnnotation method, the following is the specific example of the use of:
@Test
void test_expose(a) {
MySubClass subclass = new MySubClass(42L."the answer"."Verbose field not to serialize");
MyClass source = new MyClass(1L."foo"."bar", subclass);
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
String s = gson.toJson(source);
System.out.println(s);
// {"id":1,"name":"foo","subclass":{"id":42,"description":"the answer","otherVerboseInfo":"Verbose field not to serialize"}}
}
@Data
@AllArgsConstructor
class MyClass {
private long id;
@Expose(serialize = false, deserialize = true)
private String name;
private transient String other;
@Expose
private MySubClass subclass;
}
@Data
@AllArgsConstructor
class MySubClass {
@Expose
private long id;
@Expose
private String description;
@Expose
private String otherVerboseInfo;
}
Copy the code
The TRANSIENT keyword modified fields in Gson are not serialized and deserialized by default, which is consistent with the Java native serialization and deserialization operations.
@Since
This annotation is used to mark the version of the corresponding field or type, allowing Gson to specify the version number for serialization and deserialization. This annotation is useful when the JSON data on a Web service corresponds to a class that has multiple versions of fields.
Again, this annotation is only valid for Gson objects that are built using GsonBuilder and that use the setVersion method to specify the version number. Only the fields in the object that correspond to the version are resolved. Here is an example:
public class VersioningSupportTest {
@Test
void test(a) {
VersionedClass versionedObject = new VersionedClass();
Gson gson = new GsonBuilder().setVersion(1.0).create();
String jsonOutput = gson.toJson(versionedObject);
System.out.println(jsonOutput); // {"newField":"new","field":"old"}}}class VersionedClass {
@Since(1.1)
private final String newerField;
@Since(1.0)
private final String newField;
private final String field;
public VersionedClass(a) {
this.newerField = "newer";
this.newField = "new";
this.field = "old"; }}Copy the code
@SerializedName
This annotation is simple to use and useful. @serializedName specifies the name by which the member fields are serialized and deserialized.
public class JSONFieldNamingSupportTest {
private class SomeObject {
@SerializedName("custom_naming")
private final String someField;
private final String someOtherField;
public SomeObject(String a, String b) {
this.someField = a;
this.someOtherField = b; }}@Test
void test(a) {
SomeObject someObject = new SomeObject("first"."second");
String jsonRepresentation = gson.toJson(someObject);
System.out.println(jsonRepresentation);
// {"custom_naming":"first","someOtherField":"second"}
SomeObject someObject1 = gson.fromJson(jsonRepresentation, SomeObject.class);
System.out.println(someObject1);
// SomeObject{someField='first', someOtherField='second'}}}Copy the code
@JsonAdapter
@ JsonAdapter main function is to replace GsonBuilder. RegisterTypeAdapter method of implementation, JsonDeserializer or JsonSerializer can be specified in the @jsonAdapter (aclass.class) mode to perform the same function. And priority than GsonBuilder. RegisterTypeAdapter higher priority, because just registerTypeAdapter method reduces execution to annotate, here no longer presentation, You can see the effect by using it directly on the Result class from the custom deserialization section above.
conclusion
This article summarizes how to use the Gson framework for serialization and antisequence operations, and introduces the various features of Gson. Hopefully, it will be helpful for those who are having trouble dealing with JSON data.
Recommended reading
- Master Spring Boot Profiles
- How do I gracefully close a Spring Boot application
- Need interface management you understand?
- You must know Lombok in Java
- The New Generation of Java microservices: Nacos
The resources
- Github.com/google/gson…
- www.jianshu.com/p/e74019622…
- Juejin. Im/post / 684490…
- www.baeldung.com/gson-deseri…
- www.baeldung.com/gson-string…