Earth Guardian

You are not LATE!You are not EARLY!

0%

Gson

介绍

JSON

JSON(JavaScript Object Notation):轻量级的数据交换格式,广泛用于服务器和客户端的交互中

Gson

Google 公司发布的一个开放源代码的 Java 库,主要用途为序列化 Java 对象为 JSON 字符串,或反序列化 JSON 字符串成 Java 对象。官方仓库。主要优势有:

  • 允许已经存在的无法改变的对象转换成 Json,或者 Json 转换成已存在的对象,不需要注解就可以直接转换
  • 对泛型提供完全的支持

依赖

app/build.gradle 中增加依赖:

1
compile 'com.google.code.gson:gson:2.8.1`

基本概念

Gson 源码文档写的比较好,可以直接参考 JavaDoc 来看示例

部分特点

  • 推荐把成员变量都声明为 private
  • Java 对象一定要有一个无参构造方法,这是 Gson 实例化对象的关键,可以没有 setter/getter 方法
  • 默认不需要注解字段,所有包含在当前类的字段都默认会被序列化或者反序列化

JavaBean 的生成

  • 规则
    尽量不使用内部类或嵌套类,如果使用需要使用 static 声明?
    实际使用中内部类和嵌套类都能解析!

  • 使用工具将 Json 生成对应的 JavaBean
    先将 Json 格式化,使用 bejson 生成JavaBean

序列化/反序列化

  • Serialization
    序列化,使 Java 对象到 Json 字符串的过程
  • Deserialization
    反序列化,Json 字符串转换成 Java 对象的过程

Gson 默认会序列化和反序列化所有属性字段,但是也可以自定义,不过现在推荐使用 TypeAdapter。如下为自定义的方法:

  • 自定义序列化的一种方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Id<T> {
private final Class<T> clazz;
private final long value;

public Id(Class<T> clazz, long value) {
this.clazz = clazz;
this.value = value;
}

public long getValue() {
return value;
}
}

class IdSerializer implements JsonSerializer<T>() {
public JsonElement serialize(Id id, Type typeOfId, JsonSerializationContext context) {
return new JsonPrimitive(id.getValue());
}
}

Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdSerializer()).create();
  • 自定义反序列化的一种方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Id<T> {
private final Class<T> clazz;
private final long value;
public Id(Class<T> clazz, long value) {
this.clazz = clazz;
this.value = value;
}
public long getValue() {
return value;
}
}

class IdDeserializer implements JsonDeserializer<Id>() {
public Id deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return new Id((Class)typeOfT, id.getValue());
}
}

Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdDeserializer()).create();

JsonReader/JsonWriter

JsonReader 继承于 Reader 用来读取字符,JsonWriter 继承于 Writer 用来写入字符。

  • JsonReader
    用于将 JSON 字符串转为 Java 对象
  • JsonWriter
    用于将 Java 对象写为 JSON 字符串

TypeAdapter

TypeAdapter 是一个抽象类,可以用来自定义类型转换,它的流式 API 比树式 API 解析更高效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class PointAdapter extends TypeAdapter<Point> {
public Point read(JsonReader reader) throws IOException {
if (reader.peek() == JsonToken.NULL) {
reader.nextNull();
return null;
}
String xy = reader.nextString();
String[] parts = xy.split(",");
int x = Integer.parseInt(parts[0]);
int y = Integer.parseInt(parts[1]);
return new Point(x, y);
}
public void write(JsonWriter writer, Point value) throws IOException {
if (value == null) {
writer.nullValue();
return;
}
String xy = value.getX() + "," + value.getY();
writer.value(xy);
}
}

GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(Point.class, new PointAdapter());
// if PointAdapter didn't check for nulls in its read/write methods, you should instead use
// builder.registerTypeAdapter(Point.class, new PointAdapter().nullSafe());
...
Gson gson = builder.create();

注解关键字

包含五个注解关键字,代码路径: com.google.gson.annotations

@Expose

@Expose 注解只有在如下构造方法初始化时才生效:
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()
生效时被注解的成员变量才会参与序列化/反序列化,其他不会参与。如果不是使用该构造方法,没有任何功能。

1
2
3
4
5
6
public class User {
@Expose private String firstName;
@Expose(serialize = false) private String lastName;
@Expose (serialize = false, deserialize = false) private String emailAddress;
private String password;
}

@SerializedName

表示某个成员变量序列化/反序列化的名字。如果后台服务器使用的命名规则和 Java 规范不一致,或者返回的 JsonJava 类只有部分变量不一致时,通过 @SerializedName 注解转换一一对应关系

1
2
3
4
5
6
7
public class MyClass {
// java 字段 a 和 json 中的 name 对应
@SerializedName("name") String a;
// java 字段 b 和 json 中的 name1, name2, name3 都可以对应
@SerializedName(value="name1", alternate={"name2", "name3"}) String b;
String c;
}

@Since

只有在如下构造方法初始化时才生效:
Gson gson = new GsonBuilder().setVersion(1.0).create()
注解生效表示成员变量从哪个版本开始有效

1
2
3
4
5
6
7
8
public class User {
private String firstName;
private String lastName;
@Since(1.0) private String emailAddress;
@Since(1.0) private String password;
// address 字段不生效,因为需要版本大于 1.1 才有效
@Since(1.1) private Address address;
}

@Until

since 一样,只有在如下构造方法初始化时才生效:
Gson gson = new GsonBuilder().setVersion(1.2).create()
注解生效时表示成员变量从哪个版本开始无效

1
2
3
4
5
6
7
public class User {
private String firstName;
private String lastName;
// 当前版本为 1.2,而这两个字段只能支持到 1.1
@Until(1.1) private String emailAddress;
@Until(1.1) private String password;
}

@JsonAdapter

表示成员变量或者类上使用指定的 TypeAdapter 来进行序列化/反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@JsonAdapter(UserJsonAdapter.class)
public class User {
public final String firstName, lastName;
private User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
public class UserJsonAdapter extends TypeAdapter<User> {
@Override public void write(JsonWriter out, User user) throws IOException {
// implement write: combine firstName and lastName into name
out.beginObject();
out.name("name");
out.value(user.firstName + " " + user.lastName);
out.endObject();
// implement the write method
}
@Override public User read(JsonReader in) throws IOException {
// implement read: split name into firstName and lastName
in.beginObject();
in.nextName();
String[] nameParts = in.nextString().split(" ");
in.endObject();
return new User(nameParts[0], nameParts[1]);
}
}

特殊关键字 transient

如果被关键字 transient 申明了的字段,不会参与 Gson 的序列化和反序列化:
transient int val;

使用流程

常见问题

Gson 转换过程中的空字段

如果元素看起来是个 list,但是实际一直为空,可以直接注释掉这个字段,也就是javabean 中不需要定义 comments 字段。否则会因空报错:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 10796 path $.data.data[3].comments[0]

包含相同字段解决方案

比如数据格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"message": "success",
"data": {
"has_more": false,
"has_new_message": false,
"max_time": 1503994432,
"data": [
{
"group": {
"text": "\u513f\u5b50\u521d\u4e2d\uff0c",
"dislike_reason": [
{
"type": 1,
"id": 319,
"title": "\u6076\u641e"
},

这里面有两个 datajson 转换为 javabean 时会出现重复字段,设计 javabean 可以通过将重复的类更换类名,但字段名不变:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// json中的第一个 data,类名更换
private DataX data;

public class DataX {

private boolean has_more;
private String tip;
private boolean has_new_message;
private float max_time;
private float min_time;
private List<Data> data;

// json中的第二层 data
public class Data {

private Group group;
private int type;
private float display_time;
private float online_time;
...
}
...
}

参考文档:

  1. 官方仓库
  2. 国外经典教程
  3. Gson源码解读
  4. Gson全解析