背景
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
|
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/