Gson(又称Google Gson)是Google公司发布的一个开放源代码的Java库,主要用途为序列化Java对象为JSON字符串,或反序列化JSON字符串成Java对象。而JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成,广泛应用于各种数据的交互中,尤其是服务器与客户端的交互。
一. Gson处理对象的几个重要点
推荐把成员变量都声明称private的
没有必要用注解(@Expose 注解)指明某个字段是否会被序列化或者反序列化,所有包含在当前类(包括父类)中的字段都应该默认被序列化或者反序列化
如果某个字段被 transient 这个Java关键词修饰,就不会被序列化或者反序列化
-
下面的实现方式能够正确的处理null
当序列化的时候,如果对象的某个字段为null,是不会输出到Json字符串中的。
当反序列化的时候,某个字段在Json字符串中找不到对应的值,就会被赋值为null
如果一个字段是 synthetic的,他会被忽视,也即是不应该被序列化或者反序列化
内部类(或者anonymous class(匿名类),或者local class(局部类,可以理解为在方法内部声明的类))的某个字段和外部类的某个字段一样的话,就会被忽视,不会被序列化或者反序列化
二. Gson中的一些注解
@SerializedName注解
该注解能指定该字段在JSON中对应的字段名称
public class Box {
@SerializedName("w")
private int width;
@SerializedName("h")
private int height;
@SerializedName("d")
private int depth;
// Methods removed for brevity
}
也就是说{"w":10,"h":20,"d":30}
这个JSON 字符串能够被解析到上面的width,height和depth字段中。
@Expose注解
该注解能够指定该字段是否能够序列化或者反序列化,默认的是都支持(true)。
public class Account {
@Expose(deserialize = false)
private String accountNumber;
@Expose
private String iban;
@Expose(serialize = false)
private String owner;
@Expose(serialize = false, deserialize = false)
private String address;
private String pin;
}
需要注意的通过 builder.excludeFieldsWithoutExposeAnnotation()
方法是该注解生效。
final GsonBuilder builder = new GsonBuilder();
builder.excludeFieldsWithoutExposeAnnotation();
final Gson gson = builder.create();
@Since和@Until注解
Since代表“自从”,Until 代表”一直到”。它们都是针对该字段生效的版本。比如说 @Since(1.2)
代表从版本1.2之后才生效,@Until(0.9)
代表着在0.9版本之前都是生效的。
public class SoccerPlayer {
private String name;
@Since(1.2)
private int shirtNumber;
@Until(0.9)
private String country;
private String teamName;
// Methods removed for brevity
}
也就是说我们利用方法builder.setVersion(1.0)
定义版本1.0,如下:
final GsonBuilder builder = new GsonBuilder();
builder.setVersion(1.0);
final Gson gson = builder.create();
final SoccerPlayer account = new SoccerPlayer();
account.setName("Albert Attard");
account.setShirtNumber(10); // Since version 1.2
account.setTeamName("Zejtun Corinthians");
account.setCountry("Malta"); // Until version 0.9
final String json = gson.toJson(account);
System.out.printf("Serialised (version 1.0)%n %s%n", json);
由于shirtNumber
和country
作用版本分别是1.2之后,和0.9之前,所以在这里都不会得到序列化,所以输出结果是:
Serialised (version 1.0)
{"name":"Albert Attard","teamName":"Zejtun Corinthians"}
三. Gson 序列化
序列化基本类型
Gson gson = new Gson();
gson.toJson(1); ==> prints 1
gson.toJson("abcd"); ==> prints "abcd"
gson.toJson(new Long(10)); ==> prints 10
int[] values = { 1 };
gson.toJson(values); ==> prints [1]
序列化对象
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
将上边的pojo序列化
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
==> json is {"value1":1,"value2":"abc"}
transient 关键字防止序列化.
序列化集合
Gson gson = new Gson();
Collection ints = Lists.immutableList(1,2,3,4,5);
String json = gson.toJson(ints); ==> json is [1,2,3,4,5]
序列化集合很容易,但反序列化不易. 看之后的反序列化就知道了.
Gson解析器进行序列化
英文Serialize和format都对应序列化,这是一个Java对象到JSON字符串的过程。
接着看一个例子,下面分别是java类和以及我们期望的JSON数据:
public class Book {
private String[] authors;
private String isbn10;
private String isbn13;
private String title;
//为了代码简洁,这里移除getter和setter方法等
}
{
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
"isbn-10": "032133678X",
"isbn-13": "978-0321336781",
"authors": [
"Joshua Bloch",
"Neal Gafter"
]
}
你肯定能发现JSON数据中出现了isbn-10
和isbn-13
, 我们怎么把字段数据isbn10
和isbn13
转化为JSON数据需要的isbn-10
和isbn-13
,Gson当然为我们提供了对应的解决方案
采用上面提到的@SerializedName
注解。
public class Book {
private String[] authors;
@SerializedName("isbn-10")
private String isbn10;
@SerializedName("isbn-13")
private String isbn13;
private String title;
//为了代码简洁,这里移除getter和setter方法等
}
public class BookSerialiser implements JsonSerializer {
@Override
public JsonElement serialize(final Book book, final Type typeOfSrc, final JsonSerializationContext context) {
final JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("title", book.getTitle());
jsonObject.addProperty("isbn-10", book.getIsbn10());
jsonObject.addProperty("isbn-13", book.getIsbn13());
final JsonArray jsonAuthorsArray = new JsonArray();
for (final String author : book.getAuthors()) {
final JsonPrimitive jsonAuthor = new JsonPrimitive(author);
jsonAuthorsArray.add(jsonAuthor);
}
jsonObject.add("authors", jsonAuthorsArray);
return jsonObject;
}
}
下面对序列化过程进行大致的分析:
- JsonSerializer是一个接口,我们需要提供自己的实现,来满足自己的序列化要求。
public interface JsonSerializer {
/**
* Gson 会在解析指定类型T数据的时候触发当前回调方法进行序列化
*
* @param T 需要转化为Json数据的类型,对应上面的Book
* @return 返回T指定的类对应JsonElement
*/
public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context);
}
- 首先在上面的代码中,我们需要创建的是一个JsonElement对象,这里对应Book是一个Json串,所以创建一个JsonObject类型。
final JsonObject jsonObject = new JsonObject();
- 然后我们将相应字段里面的数据填充到jsonObject里面。
jsonObject.addProperty...
jsonObject.add...
下面是jsonObject中的添加方法:
因为JsonElement是JsonObject和JsonArray、JsonPrimitive..的抽象父类,所以最后返回的还是一个JsonElement 类型,这里对应的是jsonObject。完成了javaBean->JSON数据的转化。
同样需要配置
// Configure GSON
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Book.class, new BookSerialiser());
gsonBuilder.setPrettyPrinting();
final Gson gson = gsonBuilder.create();
final Book javaPuzzlers = new Book();
javaPuzzlers.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases");
javaPuzzlers.setIsbn10("032133678X");
javaPuzzlers.setIsbn13("978-0321336781");
javaPuzzlers.setAuthors(new String[] { "Joshua Bloch", "Neal Gafter" });
// Format to JSON
final String json = gson.toJson(javaPuzzlers);
System.out.println(json);
这里对应的是gsonBuilder.registerTypeAdapter(Book.class, new BookSerialiser())方法进行JsonSerializer的配置。在上面例子中,通过调用gsonBuilder.setPrettyPrinting();`方法还告诉了 Gson 对生成的 JSON 对象进行格式化。
四. Gson 反序列化
反序列化基本类型
int one = gson.fromJson("1", int.class);
Integer one = gson.fromJson("1", Integer.class);
Long one = gson.fromJson("1", Long.class);
Boolean false = gson.fromJson("false", Boolean.class);
String str = gson.fromJson("\"abc\"", String.class);
String anotherStr = gson.fromJson("[\"abc\"]", String.class);
反序列化对象
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
BagOfPrimitives obj2 = gson.fromJson(json, BagOfPrimitives.class);
==> obj2 is just like obj
这样就可以反序列化了.
反序列化集合
Gson gson = new Gson();
Collection ints = Lists.immutableList(1,2,3,4,5);
Type collectionType = new TypeToken>(){}.getType();
Collection ints2 = gson.fromJson(json, collectionType);
ints2 is same as ints
这里使用TypeToken
new TypeToken<传入集合或其他的类型>(){}.getType();
来取得类型,然后再在
gson.fromJson(json, collectionType);
取得反序列化的数据.
可以序列化任意对象的集合但不能反序列化.这是因为没有途径使得用户可以去提示该对象的类型。
反序列化过程中,集合必须制定特定的泛型
所有这些是有意义的,它使得你在遵循好的Java编码实践的过程中很少发生错误。
Gson解析器进行反序列化
英文parse和deserialise对应反序列化,这是一个字符串转换成Java对象的过程。
我们同样采用上面一小节的代码片段,只不过现在我们需要做的是将:
{
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
"isbn-10": "032133678X",
"isbn-13": "978-0321336781",
"authors": [
"Joshua Bloch",
"Neal Gafter"
]
}
转化为对应的Book实体类
利用@SerializedName 注解
也就是说我们的实体类Book.java可以这么写:
public class Book {
private String[] authors;
@SerializedName("isbn-10")
private String isbn10;
@SerializedName(value = "isbn-13", alternate = {"isbn13","isbn.13"})
private String isbn13;
private String title;
//为了代码简洁,这里移除getter和setter方法等
}
可以看到这里我们在
@SerializedName
注解使用了一个value
,alternate
字段,value
也就是默认的字段,对序列化和反序列化都有效,alternate
只有反序列化才有效果。也就是说一般服务器返回给我们JSON数据的时候可能同样的一个图片,表示"image","img","icon"等,我们利用@SerializedName
中的alternate
字段就能解决这个问题,全部转化为我们实体类中的图片字段。
我们在序列化的时候使用的是JsonSerialize
,这里对应使用JsonDeserializer
我们将解析到的json数据传递给Book的setter方法即可。
public class BookDeserializer implements JsonDeserializer {
@Override
public Book deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
final JsonElement jsonTitle = jsonObject.get("title");
final String title = jsonTitle.getAsString();
final String isbn10 = jsonObject.get("isbn-10").getAsString();
final String isbn13 = jsonObject.get("isbn-13").getAsString();
final JsonArray jsonAuthorsArray = jsonObject.get("authors").getAsJsonArray();
final String[] authors = new String[jsonAuthorsArray.size()];
for (int i = 0; i < authors.length; i++) {
final JsonElement jsonAuthor = jsonAuthorsArray.get(i);
authors[i] = jsonAuthor.getAsString();
}
final Book book = new Book();
book.setTitle(title);
book.setIsbn10(isbn10);
book.setIsbn13(isbn13);
book.setAuthors(authors);
return book;
}
}
和Gson序列化章节一样,我们这里接着分析我们是怎么将JSON数据解析(反序列化)为实体类的:
- 因为我们可以发现上面的JSON数据是一个
{}
大括号包围的,也就意味着这是一个Json对象。所以首先我们通过final JsonObject jsonObject = json.getAsJsonObject();`将我们的JsonElement转化为JsonObject - 通过
jsonObject.get("xxx").getAsString()
的形式获取相应String的值 - 通过
jsonObject.get("xx").getAsJsonArray();
获取相应的json数组,并遍历出其中的相应字段值 - 通过setter方法,将获取到的值设置给Book类。
- 最终返回的是 Book的对象实例。完成了JSON->javaBean的转化
- 同样需要配置
- 关于从本地流中读取Json数据可以使用
InputStreamReader
完成
// Configure Gson
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Book.class, new BookDeserializer());
Gson gson = gsonBuilder.create();
// The JSON data
try(Reader reader = new InputStreamReader(Main.class.getResourceAsStream("/part1/sample.json"), "UTF-8")){
// Parse JSON to Java
Book book = gson.fromJson(reader, Book.class);
System.out.println(book);
}
五. TypeAdapter的使用
TypeAdapter介绍
除了运用JsonSerializer和JsonDeserializer进行JSON和java实体类之间的相互转化。我们还可以利用TypeAdapter
更加高效的完成这个需求。
之前在上一篇文中提到的JsonSerializer
和JsonDeserializer
解析的时候都利用到了一个中间件-JsonElement
,比如下方的序列化过程。可以看到我们在把Java对象转化为JSON字符串的时候都会用到这个中间件JsonElement
而TypeAdapter
的使用正是去掉了这个中间层,直接用流来解析数据,极大程度上提高了解析效率。
New applications should prefer TypeAdapter, whose streaming API is more efficient than this interface’s tree API.
应用中应当尽量使用
TypeAdapter
,它流式的API相比于之前的树形解析API将会更加高效。
TypeAdapter
作为一个抽象类提供两个抽象方法。分别是write()
和read()
方法,也对应着序列化和反序列化。如下图所示:
下面就让我们来一起使用和了解TypeAdapter吧。
TypeAdapter实例
Book.java
实体类:
package com.javacreed.examples.gson.part1;
public class Book {
private String[] authors;
private String isbn;
private String title;
//为了代码简洁,这里移除getter和setter方法等
}
直接贴代码,具体序列化和反序列化的TypeAdapter
类,这里是BookTypeAdapter.java
:
public class BookTypeAdapter extends TypeAdapter {
@Override
public Book read(final JsonReader in) throws IOException {
final Book book = new Book();
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "isbn":
book.setIsbn(in.nextString());
break;
case "title":
book.setTitle(in.nextString());
break;
case "authors":
book.setAuthors(in.nextString().split(";"));
break;
}
}
in.endObject();
return book;
}
@Override
public void write(final JsonWriter out, final Book book) throws IOException {
out.beginObject();
out.name("isbn").value(book.getIsbn());
out.name("title").value(book.getTitle());
out.name("authors").value(StringUtils.join(book.getAuthors(), ";"));
out.endObject();
}
}
同样这里设置TypeAdapter
之后还是需要配置(注册),可以注意到的是gsonBuilder.registerTypeAdapter(xxx)
方法进行注册在我们之前的JsonSerializer
和JsonDeserializer
中也有使用:
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Book.class, new BookTypeAdapter());
final Gson gson = gsonBuilder.create();
下面对两个write方法和read方法进行分别的阐述:
TypeAdapter中的write方法
write()
方法中会传入JsonWriter
,和需要被序列化的Book
对象的实例,采用和PrintStream
类似的方式 写入到JsonWriter
中。
@Override
public void write(final JsonWriter out, final Book book) throws IOException {
out.beginObject();
out.name("isbn").value(book.getIsbn());
out.name("title").value(book.getTitle());
out.name("authors").value(StringUtils.join(book.getAuthors(), ";"));
out.endObject();
}
下面是上面代码的步骤:
-
out.beginObject()
产生{
,如果我们希望产生的是一个数组对象,对应的使用beginArray()
-
out.name("isbn").value(book.getIsbn()); out.name("title").value(book.getTitle());
分别获取book中的isbn和title字段并且设置给Json对象中的isbn和title。也就是说上面这段代码,会在json对象中产生:
"isbn": "978-0321336781",
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
-
out.name("authors").value(StringUtils.join(book.getAuthors(), ";"));
则会对应着:
"authors": "Joshua Bloch;Neal Gafter"
- 同理
out.endObject()
则对应着}
- 那么整个上面的代码也就会产生JSON对象:
{
"isbn": "978-0321336781",
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
"authors": "Joshua Bloch;Neal Gafter"
}
这里需要注意的是,如果没有调用 out.endObject()
产生}
,那么你的项目会报出 JsonSyntaxException
错误
Exception in thread "main" com.google.gson.JsonSyntaxException: java.io.EOFException: End of input at line 4 column 40
at com.google.gson.Gson.fromJson(Gson.java:813)
at com.google.gson.Gson.fromJson(Gson.java:768)
at com.google.gson.Gson.fromJson(Gson.java:717)
at com.google.gson.Gson.fromJson(Gson.java:689)
at com.javacreed.examples.gson.part1.Main.main(Main.java:41)
Caused by: java.io.EOFException: End of input at line 4 column 40
at com.google.gson.stream.JsonReader.nextNonWhitespace(JsonReader.java:1377)
at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:471)
at com.google.gson.stream.JsonReader.hasNext(JsonReader.java:403)
at com.javacreed.examples.gson.part1.BookTypeAdapter.read(BookTypeAdapter.java:33)
at com.javacreed.examples.gson.part1.BookTypeAdapter.read(BookTypeAdapter.java:1)
at com.google.gson.Gson.fromJson(Gson.java:803)
... 4 more
TypeAdapter中的read方法
read()
方法将会传入一个JsonReader
对象实例并返回反序列化的对象。
@Override
public Book read(final JsonReader in) throws IOException {
final Book book = new Book();
in.beginObject();
while (in.hasNext()) {
switch (in.nextName()) {
case "isbn":
book.setIsbn(in.nextString());
break;
case "title":
book.setTitle(in.nextString());
break;
case "authors":
book.setAuthors(in.nextString().split(";"));
break;
}
}
in.endObject();
return book;
}
下面是这段代码的步骤:
- 同样是通过
in.beginObject();
和in.endObject();
对应解析{
,}
- 通过
while (in.hasNext()) {
switch (in.nextName()) {
}
}
来完成每个JsonElement
的遍历,并且通过switch...case
的方法获取Json对象中的键值对。并通过我们Book实体类
的Setter
方法进行设置。
while (in.hasNext()) {
switch (in.nextName()) {
case "isbn":
book.setIsbn(in.nextString());
break;
case "title":
book.setTitle(in.nextString());
break;
case "authors":
book.setAuthors(in.nextString().split(";"));
break;
}
}
同样需要注意的是,如果没有执行in.endObject()
,将会出现JsonIOException
的错误:
Exception in thread "main" com.google.gson.JsonIOException: JSON document was not fully consumed.
at com.google.gson.Gson.assertFullConsumption(Gson.java:776)
at com.google.gson.Gson.fromJson(Gson.java:769)
at com.google.gson.Gson.fromJson(Gson.java:717)
at com.google.gson.Gson.fromJson(Gson.java:689)
at com.javacreed.examples.gson.part1.Main.main(Main.java:41)
下面给出使用TypeAdapter
的完整代码:
public static void main(final String[] args) throws IOException {
final GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(Book.class, new BookTypeAdapter());
gsonBuilder.setPrettyPrinting();
final Gson gson = gsonBuilder.create();
final Book book = new Book();
book.setAuthors(new String[] { "Joshua Bloch", "Neal Gafter" });
book.setTitle("Java Puzzlers: Traps, Pitfalls, and Corner Cases");
book.setIsbn("978-0321336781");
final String json = gson.toJson(book);
System.out.println("Serialised");
System.out.println(json);
final Book parsedBook = gson.fromJson(json, Book.class);
System.out.println("\nDeserialised");
System.out.println(parsedBook);
}
对应的编译结果为:
Serialised
{
"isbn": "978-0321336781",
"title": "Java Puzzlers: Traps, Pitfalls, and Corner Cases",
"authors": "Joshua Bloch;Neal Gafter"
}
Deserialised
Java Puzzlers: Traps, Pitfalls, and Corner Cases [978-0321336781]
Written by:
>> Joshua Bloch
>> Neal Gafter