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。很好理解。
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")
即可,代码如下:
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
。代码如下:
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 + '\'' +
'}';
}
}
则输出
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修饰的字段就会被忽略啦。
Gson的TypeAdapter
typeAdapter是灵活使用Gson的一大利器,使用场景:服务器的小伙伴传来的json为(这里使用了测试站Get数据)
{
"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就好了。
{
"args": "xx",
"headers": "xx",
"origin": "123.126.22.222",
"url": "http://httpbin.org/get?name=test&intValue=123"
}
使用typeAdapter就可以自定义解析格式。首先要自己新建TypeAdapter的子类,有点类似于实现Parcelable接口,需要实现write和read方法。
/**
* 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的过程,关键代码在于
while (in.hasNext()) {
if (!TextUtils.isEmpty(args)) args += ", ";
args += in.nextName() + "=" + in.nextString();
}
由于我们不希望Gson死板地解析args为一个jsonObject,这就需要手动空读‘{’即in.beginObject();
,循环读args中子字段但都只做拼接操作,最终将拼接好的String赋值给本地model:
model.args = args;
in.endObject();
最后别忘了返回model。
总结
Gson用法远比我之前想象的灵活(才学疏浅 - -),Gson建造者模式能够创建出各式各样策略的gson对象,本文也只是总结自己项目中用到的一些点,像复杂map的解析什么的都没有涉及,日后可以补充。