记一次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 a List of internally created objects LinkedTreeMap and since you are using raw types (should be a warning for unchecked cast)with declaration as concrete type which will create a issue when adapter actually expecting a PojoObject but internally it's something else.

由于缺失数据的类型信息, Gson返回了一个内部的LinkedTreeMap,导致后续使用时报错


发布于 2019-09-27 13:26, 最后修改于2019-09-27 13:45