本文为作者根据日常使用结合Gson源码注释及wiki所作的原创内容,转载请注明出处。
本文链接:http://www.jianshu.com/p/e740196225a4
JSON (官网) 是一种文本形式的数据交换格式,它比XML更轻量、比二进制容易阅读和编写,调式也更加方便。其重要性不言而喻。解析和生成的方式很多,Java中最常用的类库有:JSON-Java、Gson、Jackson、FastJson等。
该系列其它文章
注:此系列基于Gson 2.4。
对Gson使用很自信的大大可以点击关闭啦。
本篇文章的主要内容:
- Gson的基本用法
- 属性重命名
@SerializedName
注解的使用 - Gson中使用泛型
一、Gson的基本用法
Gson提供了fromJson()
和toJson()
两个直接用于解析和生成的方法,前者实现反序列化,后者实现了序列化。同时每个方法都提供了重载方法,我常用的总共有5个。
基本数据类型的解析
Gson gson = new Gson();
int i = gson.fromJson("100", int.class); //100
double d = gson.fromJson("\"99.99\"", double.class); //99.99
boolean b = gson.fromJson("true", boolean.class); // true
String str = gson.fromJson("String", String.class); // String
注:不知道你是否注意到了第2、3行有什么不一样没
基本数据类型的生成
Gson gson = new Gson();
String jsonNumber = gson.toJson(100); // 100
String jsonBoolean = gson.toJson(false); // false
String jsonString = gson.toJson("String"); //"String"
POJO类的生成与解析
public class User {
//省略其它
public String name;
public int age;
public String emailAddress;
}
生成JSON:
Gson gson = new Gson();
User user = new User("怪盗kidou",24);
String jsonObject = gson.toJson(user); // {"name":"怪盗kidou","age":24}
解析JSON:
Gson gson = new Gson();
String jsonString = "{\"name\":\"怪盗kidou\",\"age\":24}";
User user = gson.fromJson(jsonString, User.class);
二、属性重命名 @SerializedName 注解的使用
从上面POJO的生成与解析可以看出json的字段和值是的名称和类型是一一对应的,但也有一定容错机制(如第一个例子第3行将字符串的99.99转成double型,你可别告诉我都是字符串啊),但有时候也会出现一些不和谐的情况,如:
期望的json格式
{"name":"怪盗kidou","age":24,"emailAddress":"ikidou@example.com"}
实际
{"name":"怪盗kidou","age":24,"email_address":"ikidou@example.com"}
这对于使用PHP作为后台开发语言时很常见的情况,php和js在命名时一般采用下划线风格,而Java中一般采用的驼峰法,让后台的哥们改吧 前端和后台都不爽,但要自己使用下划线风格时我会感到不适应,怎么办?难到没有两全齐美的方法么?
我们知道Gson在序列化和反序列化时需要使用反射,说到反射就不得不想到注解,一般各类库都将注解放到annotations
包下,打开源码在com.google.gson
包下果然有一个annotations
,里面有一个SerializedName
的注解类,这应该就是我们要找的。
那么对于json中email_address
这个属性对应POJO的属性则变成:
@SerializedName("email_address")
public String emailAddress;
这样的话,很好的保留了前端、后台、Android/java各自的命名习惯。
你以为这样就完了么?
如果接中设计不严谨或者其它地方可以重用该类,其它字段都一样,就emailAddress
字段不一样,比如有下面三种情况那怎么?重新写一个?
{"name":"怪盗kidou","age":24,"emailAddress":"ikidou@example.com"}
{"name":"怪盗kidou","age":24,"email_address":"ikidou@example.com"}
{"name":"怪盗kidou","age":24,"email":"ikidou@example.com"}
为POJO字段提供备选属性名
SerializedName
注解提供了两个属性,上面用到了其中一个,别外还有一个属性alternate
,接收一个String数组。
注:alternate
需要2.4版本
@SerializedName(value = "emailAddress", alternate = {"email", "email_address"})
public String emailAddress;
当上面的三个属性(email_address、email、emailAddress)都中出现任意一个时均可以得到正确的结果。
注:当多种情况同时出时,以最后一个出现的值为准。
Gson gson = new Gson();
String json = "{\"name\":\"怪盗kidou\",\"age\":24,\"emailAddress\":\"ikidou_1@example.com\",\"email\":\"ikidou_2@example.com\",\"email_address\":\"ikidou_3@example.com\"}";
User user = gson.fromJson(json, User.class);
System.out.println(user.emailAddress); // ikidou_3@example.com
三、Gson中使用泛型
上面了解的JSON中的Number、boolean、Object和String,现在说一下Array。
例:JSON字符串数组
["Android","Java","PHP"]
当我们要通过Gson解析这个json时,一般有两种方式:使用数组,使用List。而List对于增删都是比较方便的,所以实际使用是还是List比较多。
数组比较简单
Gson gson = new Gson();
String jsonArray = "[\"Android\",\"Java\",\"PHP\"]";
String[] strings = gson.fromJson(jsonArray, String[].class);
但对于List将上面的代码中的 String[].class
直接改为 List<String>.class
是行不通的。对于Java来说List<String>
和List<User>
这俩个的字节码文件只一个那就是List.class
,这是Java泛型使用时要注意的问题 泛型擦除。
为了解决的上面的问题,Gson为我们提供了TypeToken
来实现对泛型的支持,所以当我们希望使用将以上的数据解析为List<String>
时需要这样写。
Gson gson = new Gson();
String jsonArray = "[\"Android\",\"Java\",\"PHP\"]";
String[] strings = gson.fromJson(jsonArray, String[].class);
List<String> stringList = gson.fromJson(jsonArray, new TypeToken<List<String>>() {}.getType());
注:TypeToken
的构造方法是protected
修饰的,所以上面才会写成new TypeToken<List<String>>() {}.getType()
而不是 new TypeToken<List<String>>().getType()
泛型解析对接口POJO的设计影响
泛型的引入可以减少无关的代码,如我现在所在公司接口返回的数据分为两类:
{"code":"0","message":"success","data":{}}
{"code":"0","message":"success","data":[]}
我们真正需要的data
所包含的数据,而code
只使用一次,message
则几乎不用。如果Gson不支持泛型或不知道Gson支持泛型的同学一定会这么定义POJO。
public class UserResponse {
public int code;
public String message;
public User data;
}
当其它接口的时候又重新定义一个XXResponse
将data
的类型改成XX,很明显code
,和message
被重复定义了多次,通过泛型的话我们可以将code
和message
字段抽取到一个Result
的类中,这样我们只需要编写data
字段所对应的POJO即可,更专注于我们的业务逻辑。如:
public class Result<T> {
public int code;
public String message;
public T data;
}
那么对于data
字段是User
时则可以写为 Result<User>
,当是个列表的时候为 Result<List<User>>
,其它同理。
PS:嫌每次 new TypeToken<Result<XXX>
和 new TypeToken<Result<List<XXX>>
太麻烦, 想进一步封装? 查看我的另一篇博客:** 《搞定Gson泛型封装》 **
结语
本文主要通过代码向各位读者讲解了Gson的基本用法,以后还会更新更多更高级的用法,如果你还不熟悉 注解和泛型 那么你要多多努力啦。
如果你有其它的想了解的内容(不限于Gson)请给我留言评论,水平有限,欢迎拍砖。
4月6日补充
有说看不懂Result那段怎么个简化法,下面给个两个完整的例子,User和List<User> 。
没有引入泛型之前时写法:
public class UserResult {
public int code;
public String message;
public User data;
}
//=========
public class UserListResult {
public int code;
public String message;
public List<User> data;
}
//=========
String json = "{..........}";
Gson gson = new Gson();
UserResult userResult = gson.fromJson(json,UserResult.class);
User user = userResult.data;
UserListResult userListResult = gson.fromJson(json,UserListResult.class);
List<User> users = userListResult.data;
上面有两个类UserResult
和UserListResult
,有两个字段重复,一两个接口就算了,如果有上百个怎么办?不得累死?所以引入泛型。
//不再重复定义Result类
Type userType = new TypeToken<Result<User>>(){}.getType();
Result<User> userResult = gson.fromJson(json,userType);
User user = userResult.data;
Type userListType = new TypeToken<Result<List<User>>>(){}.getType();
Result<List<User>> userListResult = gson.fromJson(json,userListType);
List<User> users = userListResult.data;
看出区别了么?引入了泛型之后虽然要多写一句话用于获取泛型信息,但是返回值类型很直观,也少定义了很多无关类。
网友评论
{
0={
img=0.0,
name=非会员,
qualify_amount=0.0
},
1={
img=1.0,
name=一级会员,
qualify_amount=100.0
},
2={
img=2.0,
name=二级会员,
qualify_amount=300.0
},
3={
img=3.0,
name=三级会员,
qualify_amount=500.0
1. 当原始的类源码不能修改.
2. 或者有些需要, 有些不需要, 我想要灵活设置, 有办法吗?
为什么TypeToken的构造方法是protected修饰的,就需要写成new TypeToken<List<String>>() {}.getType() 而不是 new TypeToken<List<String>>().getType()呢? 希望能得到一些提示~
//得到list<t>
Type listType = new ParameterizedTypeImpl(List.class, new Class[]{clas});
//得到backresult<list<T>>
Type type = new ParameterizedTypeImpl(BackResult.class, new Type[]{listType});
BackResult<List<T>> backResult = new Gson().fromJson(jsonObject, type);
return backResult;
}
请教楼主, java.lang.IllegalArgumentException: Expected a Class, ParameterizedType, or GenericArrayType, but <com.github.darren.smilemo.bean.ParameterizedTypeImpl@90213f6> is of type com.github.darren.smilemo.bean.ParameterizedTypeImpl
说我传的类型不正确,这个是什么情况额
源码解析:http://suo.im/1FKU2k
矜
想要结果是 "><" 要怎么处理 ?
.setPrettyPrinting()
.disableHtmlEscaping()
.create();
http://baobab.kaiyanapp.com/api/v4/tabs/selected
咨询下像上面这种的多类型集合json解析,有没有更简便的方法,retrofit该怎么处理
Type userType = new TypeToken<Result<User>>(){}.getType();
Result<User> userResult = gson.fromJson(json,userType);
User user = userResult.data;
只用 userType 就可以获取除了User的信息吗?比如说 code和message?
UserListResult userListResult = gson.fromJson(json,
UserListResult.class);
List<User> userList = userListResult.getData();
for (int i = 0; i < userList.size(); i++) {
Log.v("TAG", "=============>>" + userList.get(i).getName() + ":"
+ userList.get(i).getAge() + ":"
+ userList.get(i).getEmailAddress());
}
或者
Type userListType = new TypeToken<Result<List<User>>>() {
}.getType();
Result<List<User>> userListRes = gson.fromJson(json, userListType);
List<User> userList = userListRes.getData();
for (int i = 0; i < userList.size(); i++) {
Log.v("TAG", "=============>>" + userList.get(i).getName() + ":"
+ userList.get(i).getAge() + ":"
+ userList.get(i).getEmailAddress());
}
这样子写都会报错,错在获取userListType上,03-03 16:35:19.005: E/AndroidRuntime(4792): Caused by: java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 10 path $.data
另外的两种方法都可以获取到数据,找了一天了,不知问题所在。(为什么评论不能放截图呢~呜呜~)
String json = "{\"data\":{\"name\":\"王大锤\","
+ "\"email_address\":\"bbbbb\",\"age\":\"24\"},"
+ "\"code\":\"12345\",\"message\":\"同学你好!\"}";
String json = "{\"data\":[{\"name\":\"王大锤\","
+ "\"emailAddress\":\"bbbbb\",\"age\":\"24\"}],"
+ "\"code\":\"12345\",\"message\":\"同学你好!\"}";
Result<User> userResult = gson.fromJson(json,userType);
User user = userResult.data;但是我这样写是没问题的,是可以获取到值de
result的data是泛型T,new TypeToken<result<List<T>>的时候就会变成LinkedTreeMap。
那么针对result返回值是否list还要作进一步判断,从而调用不同的方法来获取type,
如果我想在接收部分统一处理返回值,请问怎么通过同一种途径获取这个type呢?
```java
Type type = TypeBuilder
.newInstance(Result.class)
.beginSubType(List.class)
.addTypeParam(clazz)
.endSubType()
.build();
```
如果不是list,则是
```java
Type type = TypeBuilder
.newInstance(Result.class)
.addTypeParam(clazz)
.build();
```
list比非list多了一步处理,那么在未确定T的类型之前,则我的返回值是这样的
```
public class Result<T>{
int code;
T message;
}
```
在网络请求的返回部分这样处理
```java
Type type = new TypeToken<T>(){}.getType();
Result result = new Gson().fromJson(jsonStr, type);
```
那么问题来了,我只有一个jsonStr(则服务器返回值),我并不知道里面包含的内容中message是list还是只是一个json object,如果要以你文章的那种处理方式,我得先知道是否list,然后再分别处理,那我这里请问该怎么做呢?
比如这样:{
"client_token": "AjD0BlcUEQMMJeN32G24VFRMvW7lRmlWkZSsuXUfl6xk",
"client_type": "android",
"current_version": "1.0.0",
"signature": "MTg0NTU3NzU4ODg6Z3hjY2ZmZmZmZmY=\n"
}
这个signature字段的值 末尾的换行符
如果使用get 当解析错误时会崩溃,opt则会打印错误log直接return
那么Gson会怎么处理呢? 我关心的是,如果我的项目换成gson,那么如果避免未捕获的异常出来时候,程序不会崩溃。 谢谢
正常情况返回是这样
{
"status": 1,
"message": "操作成功",
"data": {
"id": 6,
"username": "测试"
}
}
public class User {
public int status;
public String message;
public Info data;
public class Info {
public int id;
public String username;
}
}
解析User user = new Gson().fromJson(result, User.class);
这样是没有问题的,不过可能请求参数出错,服务端返回的是
{
"status": 0,
"message": "缺少参数",
"data": ""
}
这样解析就出错,这种情况要怎么处理?
知乎提问链接https://www.zhihu.com/question/53434568?guide=1
我用的是retrofit,在自定义的Converter里这样处理
if (!TextUtils.isEmpty(result) && result.contains("\"data\":\"\"")) {
result = result.replace("\"data\":\"\"", "\"data\":{}");
}
然后再解析,感觉超不好。想让php改,口水说干都不肯改。。。
Type superClass = getClass().getGenericSuperclass();
Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
效率如何?
看到一半的时候,拉到底点了喜欢进行收藏文章,
评论看到一半的时候,拉倒顶部点了订阅作者,希望出更多的东西,多了解一些工具类,对编码效率很有帮助
Type userType = new TypeToken<Result<User>>(){}.getType();
Result<User> userResult = gson.fromJson(json,userType);
User user = userResult.data;
和
Type userListType = new TypeToken<Result<List<User>>>(){}.getType();
Result<List<User>> userListResult = gson.fromJson(json,userListType);
List<User> users = userListResult.data;
封装成工具类,但是不知道怎么弄,自己弄了一个不知道是否是泛型的层级太多还是什么原因,一直数据类型异常,可以帮忙封装让我学习一下吗?
正确:
{
"status": "200",
"data": {
"str": "123"
}
}
失败:
{
"data": "错误参数!",
"status": "1000"
}
遇到这样的情况。我使用的是Retrofit网络请求框架。目前所知的有一种解决方案,就是直接获取到JSON,解析成一个只有status的实体,然后判断是不是200。但是不优美。Gson应该有对应的解决方案。求解答!!!
为什么你的json字符串里会带有"\",以前都没有遇到过
比如服务器返回的Json是:{"name":,"age":24,"emailAddress":"ikidou@example.com"}。
name字段啥东西都没有,解析时就会报错,求解这种如何处理。
Type type = new TypeToken<Result<T>>(){}.getType();
Result<T> result = gson.fromJson(json,type);
解决。超赞。还是自己理解不深刻。
弱弱地问一下,是不是只要从`json`解析到带有泛型的类就必须使用`TypeToken`啊?没有其他的方法了么?
TypeToken<ArrayList<User>>(){}.getType() 你也可以用 new ArrayList<User>{}..getClass().getGenericSuperclass(); 代替,你愿意用哪个?
{"total":1,"rows":[{"id": "3" ,"titles": "明天放假" ,"settime": "2016/5/5 17:30:24" ,"states": "1" ,"fqBumen": "信息中心" }]}
public class Result{
public int total;
public List<Data> rows;
}
public class Data{
public String id;
public String titles;
public String settime;
public String states;
public String fqBumen;
}
Result result = gson.fromJson("{.......}",Result.class);
for (Data data:result.rows){
// TODO
}
new TypeToken<T>(){}
因为TypeToken的构造函数是protected, 所以要加{}, 等于是写了个匿名内部类, 这不是扯淡吗?别人protected了, 就肯定提供了方法给你用啊
Type type = TypeToken.get(MovieEntity.class).getType();
可以这样拿到Type, 正常调用gson的fromJson, 也可以拿到TypeAdapter后直接调用fromJson
TypeAdapter<MovieEntity> adapter = gson.getAdapter(TypeToken.get(MovieEntity.class));
MovieEntity movieEntity = adapter.fromJson(json);
我看了Retrofit的GsonConverterFactory的源码并且跳到TypeToken的源码里看了
TypeToken.get方法
public static <T> TypeToken<T> get(Class<T> type) {
return new TypeToken<T>(type);
}
不也是new了TypeToken吗? 并不需要继承啊
我只能呵呵了,喷之前先确认一下你自己真的搞懂了么
首先Class本身就实现了Type接口,所以你的
Type type = TypeToken.get(MovieEntity.class).getType();
根本就是多余的:
Type type = MovieEntity.class;
也就是说
TypeAdapter<MovieEntity> adapter = gson.getAdapter(TypeToken.get(MovieEntity.class));
也可以直接写成
TypeAdapter<MovieEntity> adapter = gson.getAdapter(MovieEntity.class);
TypeToken存在的意义就是为了获取带泛型而存在的,为啥人家设计成protected而不是private或者default?protected的目的就是让你继承!你可以试试不使用继承TypeToken的方式用TypeToken.get获取一下Result<User>的Type试试?
官方文档:https://github.com/google/gson/blob/master/UserGuide.md#TOC-Serializing-and-Deserializing-Generic-Types
大致意思如下代码:
//test1
String t1 = "{\"code\":9999,\"message\":\"000\",\"data\":{\"name\":\"zss\",\"age\":\"23\"}}";
String t2 = "{\"code\":9999,\"message\":\"000\",\"data\":[]}";
Result r1 = gson.fromJson(t1, Result.class);
KJLoger.debug(r1.toString());
Result r2 = gson.fromJson(t2, Result.class);
KJLoger.debug(r2.toString());
if(r2.data.contains("[")){
//空
}else if(r2.data.contains("{")){
Person person = gson.fromJson(r2.data,Person.class);
}
然而,如果是这样的话,那么你的通过服务器数据解析的bean并不能是你的Javabean,
所以的确是很麻烦的。 如果想要统一 ,让服务器解析的数据 直接变成你需要用的Javabean,那么 你要和后台沟通好。
Result<List<User>> userListResult = gson.fromJson(json,userType);
写错了。userListType 写错成 userType
但是服务器返回这坑爹数据,一条是以前的数据,一条是现在的
能不能用gson同时解析啊?
头疼死我了,现在我只能用object来忽略它
有没有解决办法
[3,7,"26368","player"]
{"id": 111,"name": "测试"}
private String code;
private String mess;
private T data;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public String getMess() {
return mess;
}
public void setMess(String mess) {
this.mess = mess;
}
}
大哥谢谢你,又一次这么细致的解答。还有就是你封装的那result<T> 写完整就是我写的上面那个样嘛,构造方法什么的都不需要了??? 今天还把这个运用到项目里面去了呢,没成,刚才看了你写的那,知道怎么写了。。大哥,谢谢你。。
public int code;
public String message;
public T data;
}
大哥,你好,封装的这个,为什么是静态类啊??? 怎么使用呢,糊涂了。还望大哥解答一下啊
在你最后一个例子的时候,服务器返回的数据中data字段类型不固定,比如请求成功data是一个List,不成功的时候是String类型,这样前端在使用泛型解析的时候,怎么去处理呢?
public class UserResponse {
public int code;
public String message;
public User data;
}
出错时是:
public class UserResponse {
public int code;
public String message;
}
gson.fromJson(responseString, 对象.class),实际的“对象”该怎么写呢,再反序列化之前也不知道是正常的还是错误的。
Gson在GsonBuilder中提供了一个叫 “registerTypeAdapter”的API(之后文章会单独讲),当你需要对某个类型按自己的意思进行时,可以使用该API,比如你这里,需要对int进行处理,但有可能返回空字符串,那么流程属于反序列化,所以:
Gson gson = new GsonBuilder().registerTypeAdapter(int.class, new JsonDeserializer<Integer>() {
@ Override
public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try {
return json.getAsInt();
} catch (Exception e) {
return 0;
}
}
}).create();
String json = "{\"name\":\"怪盗kidou\",\"age\":\"\"}";
User user = gson.fromJson(json, User.class);
System.out.println(user.age); // 0
JSON.parseObject(jsonObject, new TypeReference<Result<User>>() {}.getType());
总共就那么几个方法,看方法签名也大概能猜哪个方法吧