记一次Android解析Json踩坑
Android 2019-09-27 13:26 1201
最近做一个Android课的项目
后台返回json如下:
{
"success":true,
"code":"200",
"msg":"查询成功",
"data":[
{
"id":1,
"title":"新闻1",
"content":"内容1",
"author":1,
"category":0,
"status":1,
"createTime":1449504000000,
"updateTime":null
},
{
"id":2,
"title":"新闻2",
"content":"内容2",
"author":1,
"category":0,
"status":1,
"createTime":1449504000000,
"updateTime":null
}
]
}
我的做法是,直接将后台返回的实体类直接拷贝到Android项目中:
public class ResponseWrapper {
/**
* 是否成功
*/
private boolean success;
/**
* 返回码
*/
private String code;
/**
* 返回信息
*/
private String msg;
/**
* 返回数据
*/
private Object data;
}
然后正常okhttp请求:
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(IpAddress.SERVER_IP+"/news/all").build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
ResponseWrapper responseWrapper = new Gson().fromJson(response.body().string(),ResponseWrapper.class);
newsList = (List<News>) responseWrapper.getData();
Message message = handler.obtainMessage();
handler.sendMessage(message);
}
});
然后报错
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to team.mywzll.onenews.entity.News
研究了许久,发现需要给返回的实体类也就是ResponseWrapper添加范型,不能是Object类型,然后修改成如下:
public class ResponseWrapper<T> {
private boolean success;
private String code;
private String msg;
private T data;
}
添加了范型后,对应的string转对象也需要修改:
ResponseWrapper responseWrapper = new Gson().fromJson(response.body().string(),new TypeToken<ResponseWrapper<List<News>>>(){}.getType());
再次运行:
com.google.gson.JsonSyntaxException: 1449504000000 ... Caused by: java.text.ParseException: Failed to parse date ["1449504000000"]: Invalid time zone indicator '0' ... Caused by: java.lang.IndexOutOfBoundsException: Invalid time zone indicator '0'
面临崩溃,又百度许久,发现是由于返回的对象中存在date类型的字段, 需要这样,具体为什么我也不明白:
builder.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return new Date(json.getAsJsonPrimitive().getAsLong());
}
});
Gson gson = builder.create();
ResponseWrapper responseWrapper = gson.fromJson(rep,new TypeToken<ResponseWrapper<List<News>>>(){}.getType());
这时候解析终于正常了,
于是我又想用更装逼的retrofit进行网络请求:
public interface NewsInterface {
@GET("/news/all")
Call<ResponseWrapper> getAllNews();
}Retrofit retrofit = RetrofitUtils.getInstance();
NewsInterface newsInterface = retrofit.create(NewsInterface.class);
newsInterface.getAllNews().enqueue(new Callback<ResponseWrapper>() {
@Override
public void onResponse(Call<ResponseWrapper> call, Response<ResponseWrapper> response) {
ResponseWrapper responseWrapper = response.body();
newsList = (List<News>) responseWrapper.getData();
HomeListAdapter adapter = new HomeListAdapter(getContext(),newsList);
homeListView.setAdapter(adapter);
}
@Override
public void onFailure(Call<ResponseWrapper> call, Throwable t) {
t.printStackTrace();
}
});
还是上面第一个错误一样无法将com.google.gson.internal.LinkedTreeMap转为我的news实体类
然后百度,试了好几次都不行,然后看见一个stackoverflow的提问:
https://stackoverflow.com/questions/46296747/com-google-gson-internal-linkedtreemap-cannot-be-cast
的采纳答案:
意思是retrofit请求接口返回值没有指定范型,于是我修改:
public interface NewsInterface {
@GET("/news/all")
Call<ResponseWrapper<List<News>>> getAllNews();
}
然后再试就又报之前那个json语法错误,还是date类型变量的问题,试了一下,可以把实体类date类型改成string勉强解决,但那样太low了
还是苦苦的查资料,发现可以在retrofit中添加gson对象,于是我还是用了上面的GsonBuilder的方法,把生成的gson对象传给retrofit:
public class RetrofitUtils {
private static Retrofit mInstance;
private static Gson gson;
static {
GsonBuilder builder = new GsonBuilder();
// Register an adapter to manage the date types as long values
builder.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return new Date(json.getAsJsonPrimitive().getAsLong());
}
});
gson = builder.create();
}
public static Retrofit getInstance(){
if (mInstance ==null){
synchronized (RetrofitUtils.class){
if (mInstance ==null){
mInstance = new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create(gson)).baseUrl(IpAddress.SERVER_IP).build();
}
}
}
return mInstance;
}
}
最后问题终于解决!
总结:
如果服务端要统一返回接口而封装返回类型,
那么在Android端进行解析时要明确指定返回数据的具体类型,例如我这里返回json的data字段为一个List<News> 因此不能照搬服务端的返回实体类,需要添加范型,
并在使用gson进行解析时指定范型,目前我只知道 new TypeToken<ResponseWrapper<List<News>>>(){}.getType(), 这种方法
如果使用retrofit的话只需在api接口对应方法的返回值指定范型即可
出现类型转换失败的原因应该就是stackoverflow上回答的一样:
due to the missing type information ,gson
is returning aList
of internally created objectsLinkedTreeMap
and since you are usingraw
types (should be a warning for unchecked cast)with declaration as concrete type which will create a issue when adapter actually expecting aPojoObject
but internally it's something else.
由于缺失数据的类型信息, Gson返回了一个内部的LinkedTreeMap,导致后续使用时报错
发布于 2019-09-27 13:26, 最后修改于2019-09-27 13:45
© 2019 - ZXQ's Diary - zhangxiaoqiang.top