Gson使用技巧小结

背景

json格式在移动端开发中再熟悉不过了,相较xml等格式,json有易读、体积小等优点。在解析json格式时,个人比较习惯使用Google的gson工具包,之前看过gson和阿里fastjson的性能比较,貌似gson在数据量不大时性能更好。目前项目级别使用gson完全能够胜任。
开始的时候使用gson只是简单的新建Gson实例,调用from方法解析成对应model类。随着业务发展和http模块的升级,简单的toJson和from方法已经不能满足需求了。因为接口返回数据的差异性,不同情形下可能使用不同的解析策略,最简单的方法当然是针对每个接口返回的数据使用相对应的model,但这样很容易造成model对象过多过杂难以管理和分辨。另外接口返回数据命名策略也可能因人而异,下划线和驼峰式都有可能,为了保证Application端代码的一致性,就要想办法把下划线风格的转为驼峰式风格。这些需求gson通通能够解决。

Gson基础

from和toJson方法,分别用于json格式字符串转为Model对象、对象转json。很好理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void baseGson() {
String json = "{\"name\":\"gson\",\"date\":\"2015/12/29\"}";
BaseGson fromJson = getSimpleGson().fromJson(json, BaseGson.class);

String toJson = getSimpleGson().toJson(fromJson);
}

static class BaseGson {
public String name;
public String date;

@Override
public String toString() {
return "BaseGson{" +
"name='" + name + '\'' +
", date='" + date + '\'' +
'}';
}
}

注意这里的fromJson方法后可以跟type类型或class类型。解析List时多用TypeToken,如下:

new TypeToken<List<BaseGson>>(){}.getType();

Gson注解

介绍下常用几个注解,Expose和SerializedName。

SerializedName

举个例子,解决接口和本地model风格差异问题,比如返回的字段mobile_phone要解析为mobilePhone,只需在本地model类中添加注解@SerializedName("mobile_phone")即可,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void annotataionGson(){
String json = "{\"name\":\"gson\",\"date\":\"2015/12/29\", \"mobile_phone\":\"13111111111\"}";
BaseGson fromJson = getSimpleGson().fromJson(json, BaseGson.class);

Log.d("Gson", fromJson.toString());
}

static class BaseGson {
public String name;
public String date;
@SerializedName("mobile_phone")
public String mobilePhone;

@Override
public String toString() {
return "BaseGson{" +
"name='" + name + '\'' +
", date='" + date + '\'' +
", mobilePhone='" + mobilePhone + '\'' +
'}';
}
}

输出为
12-29 05:59:27.490 15245-15245/? D/Gson: BaseGson{name='gson', date='2015/12/29', mobilePhone='13111111111'}

相当方便吧,SerializedName后跟的字段就是转化后/被转化前json字符串时显示的字段。此外,除了注解Gson还提供Builder方式建造不同策略的gson对象,例如

Gson policyGson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) .create();

使用这样的policyGson,toJson时会按照“小写下划线”格式输出(toJson)。不过对@SerializedName不生效,即优先级小于@SerializedName

Expose

用来标示toJson时输出的字段。
比如对name字段使用Expose。同时开启excludeFieldsWithoutExposeAnnotation。代码如下:

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
public static void annotataionGson(){
String json = "{\"name\":\"gson\",\"date\":\"2015/12/29\", \"mobile_phone\":\"13111111111\"}";
Gson g = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
BaseGson fromJson = g.fromJson(json, BaseGson.class);

Log.d("Gson", fromJson.toString());

Log.d("Gson", g.toJson(fromJson));
}

static class BaseGson {
@Expose
public String name;
public String date;
@SerializedName("mobile_phone")
public String mobilePhone;

@Override
public String toString() {
return "BaseGson{" +
"name='" + name + '\'' +
", date='" + date + '\'' +
", mobilePhone='" + mobilePhone + '\'' +
'}';
}
}

则输出

1
2
12-29 08:02:08.922 26266-26266/? D/Gson: BaseGson{name='gson', date='null', mobilePhone='null'}
12-29 08:02:08.926 26266-26266/? D/Gson: {"name":"gson"}

除此,还有根据关键字来筛选序列化、反序列化时要忽略的字段,new GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).create();这样transient修饰的字段就会被忽略啦。

1.1 deserialize (boolean) 反序列化 默认true 例:@Expose(deserialize=false)
1.2 serialize (boolean) 序列化 默认true 例:@Expose(serialize=false)
使用new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();创建Gson对象,没有@Expose注释的属性将不会被序列化。

Gson的TypeAdapter

typeAdapter是灵活使用Gson的一大利器,使用场景:服务器的小伙伴传来的json为(这里使用了测试站Get数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"args": {
"intValue": "123",
"name": "test"
},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, sdch",
"Accept-Language": "zh-CN,zh;q=0.8",
"Cookie": "_ga=GA1.2.867290806.1451279087",
"Host": "httpbin.org",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36"
},
"origin": "123.126.22.222",
"url": "http://httpbin.org/get?name=test&intValue=123"
}

很不幸本地model类没有考虑这么复杂,所有字段均为String类型,且并不需要嵌套过深过细的数据结构。要是能转化为以下数据结构对应的model就好了。

1
2
3
4
5
6
{
"args": "xx",
"headers": "xx",
"origin": "123.126.22.222",
"url": "http://httpbin.org/get?name=test&intValue=123"
}

使用typeAdapter就可以自定义解析格式。首先要自己新建TypeAdapter的子类,有点类似于实现Parcelable接口,需要实现write和read方法。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* Created by opticalix@gmail.com on 15/12/28.
*/
public class HttpBinTypeAdapter extends TypeAdapter<HttpBinModel> {
@Override
public void write(JsonWriter out, HttpBinModel value) throws IOException {
//value write to json
if (value == null) {
out.nullValue();
} else {
out.beginObject();
out.name("args").value(value.args);
out.name("headers").value(value.headers);
out.name("origin").value(value.origin);
out.name("url").value(value.url);
out.endObject();
}
}
@Override
public HttpBinModel read(JsonReader in) throws IOException {
//read json, return model
if (in.peek() == JsonToken.NULL) {
return null;
} else {
in.beginObject();
HttpBinModel model = new HttpBinModel();
if (in.nextName().equals("args")) {
in.beginObject();
String args = "";
while (in.hasNext()) {
if (!TextUtils.isEmpty(args)) args += ", ";
args += in.nextName() + "=" + in.nextString();
}
model.args = args;
in.endObject();
}
if (in.nextName().equals("headers")) {
in.beginObject();
String headers = "";
while (in.hasNext()) {
if (!TextUtils.isEmpty(headers)) headers += ", ";
headers += in.nextName() + "=" + in.nextString();
}
model.headers = headers;
in.endObject();
}
if (in.nextName().equals("origin")) {
model.origin = in.nextString();
}
if (in.nextName().equals("url")) {
model.url = in.nextString();
}
in.endObject();
return model;
}
}
}

write比较简单,如果model不为空,则输出model中各个字段到JsonWriter。注意out.beginObject();out.endObject();,它处理的是json中的大小括号。
read方法,实现的是从reader中恢复model的过程,关键代码在于

1
2
3
4
while (in.hasNext()) {
if (!TextUtils.isEmpty(args)) args += ", ";
args += in.nextName() + "=" + in.nextString();
}

由于我们不希望Gson死板地解析args为一个jsonObject,这就需要手动空读‘{’即in.beginObject();,循环读args中子字段但都只做拼接操作,最终将拼接好的String赋值给本地model:

1
2
model.args = args;
in.endObject();

最后别忘了返回model。

总结

Gson用法远比我之前想象的灵活(才学疏浅 - -),Gson建造者模式能够创建出各式各样策略的gson对象,本文也只是总结自己项目中用到的一些点,像复杂map的解析什么的都没有涉及,日后可以补充。

参考:
Gson TypeAdapter Example
gson的@Expose注解和@SerializedName注解
转自:https://opticalix.github.io/2016/01/03/gson-skill/