这几天因为开发需求对项目中Json的解析做了一下整理。在整理的过程中遇到比较大的问题,就是后端没按约定返回字段值,以及空字符串(”“、“null”)等情况。某度和某哥了一下,发现遇到这个问题的朋友还是挺多的。于是趁热打铁总结了一下解决方案奉献给大家。
Gson和FastJson的恩怨情仇
FastJson是阿里开源的一个Json解析项目,其内部使用了各种方案使得Json序列化和反序列化的速度提升到了极致。而Gson则是Google开源的,据说可以解析一些FastJson解析不了的超复杂json结构(这个本人也没实际验证过,欢迎大家亲自去实践检验),解析速度相对要慢些。
如果不是十分地追求解析的效率,或者遇到一些无法解析的json结构,我觉得选择FastJson或者Gson都是可以的。
不过相信很多人的项目都集成了不止一种的Json解析库,FastJson、Gson、Jackson之类都可能混搭着使用,我们的项目也不例外,同时使用了FastJson和Gson。我在整理Json解析的过程中,考虑到很多开源项目和Google官方的支持,想将Json解析写到一个工具类中,统一换成用Gson解析。但实践后发现了个比较严重的问题。
FastJson在反序列化的时候,是对大小写不敏感的。也就是如下面这样的json串,“DaTa”字段的大小写不一,也可以解析到“data”字段。
{ "DaTa": "android" }
private String data;
而在序列化的时候,则默认又是会将首字母置为小写。如下
private String Data;
会序列化成
{“data”: "android" }
当然,这个问题可以用@JSONField注解来解决
@JSONField(name = "Data")
private String data; \\注意这里要小写开头
则会序列化成
{“Data”: "android" }
而相对于Gson,则完全是对字段名大小写敏感的,无论是序列化还是反序列化。也就是说,如果我们之前的模型没注意大小写的规范,用FastJson将后台json解析成前台对象时,是完全没问题的;而如果换成Gson则有可能解析失败。
这个坑埋得还是比较深,毕竟谁也没办法保证经过那么多手的代码,在属性大小写上是完全正确的。鉴于这个问题,目测还是得Gson和FastJson两者并行使用了。所以如果打算从FastJson转到Gson的朋友们,要切记注意这个细节!
异常情况
假设我们和后端约定Json数据格式如下:
{ "code":1 ,"msg":"成功" ,"Data":{"id":1,"name":"王小二" } }
{ "code":1 ,"Data":[{"id":1,"name":"王小二" },{"id":2,"name":"马大三" }] }
于是数据模型就可以这样定义:
/** * HttpResult.java */
public class HttpResult<T> {
private int code;
private String msg;
private T Data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return Data;
}
public void setData(T data) {
Data = data;
}
}
/** * UserInfo.java */
public class UserInfo {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
解析过程就像下面这么写。其中,JsonParseUtil是封装的Json解析工具类,文章最后会有全部源码;GenericType则是为了统一FastJson和Gson两个库对泛型解析的通用类,和FastJson的TypeReference、Gson的TypeToken类似,都是通过ParameterizedType来获取泛型类型指定的实际类型。
HttpResult<UserInfo> httpResult = JsonParseUtil.parseToGenericObject(jsonStr,new GenericType<HttpResult<UserInfo>>(){});
/** * JsonParseUtil.java */
public static <T> T parseToGenericObject(String dataStr,GenericType<T> genericType) {
if (TextUtils.isEmpty(dataStr)) {
return null;
}
//Gson解析
/*T t = getSingleton().fromJson(dataStr,genericType.getType());*/
//FastJson解析
T t = JSON.parSEObject(dataStr,genericType.getType());
return t;
}
一切似乎都那么顺其自然、一切似乎都那么的简单……噢耶!提交代码,准备下班了!
啪~正准备关机的时候,程序突然崩掉了,无论用FastJson还是Gson都一个样,仔细看了下后端返回的Json串,发现了问题所在,坑终究还是出现了。
情景1-定义整型字段,返回字符串“”
后台返回如下:
{ "code":1 ,"Data":{"id":"","name":"" } }
属性id是定义了int类型的,却返回了空字符串“”.
对于这种情况FastJson是做了容错处理的,底层直接会将字符串转成整型,当然如果非法字符转Integer失败也会报错。
相对来说Gson则没那么智能,不过我们可以通过实现JsonDeserializer接口,自定义序列化来解决。
/** * IntegerGsonDeserializer.java */
public class IntegerGsonDeserializer implements JsonDeserializer<Integer> {
@Override
public Integer deserialize(JsonElement jsonElement,Type type,JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
try {
//默认后台返回"" 或者 "null",则设置默认值为 0
if (jsonElement.getAsString().equals("") || jsonElement.getAsString().equals("null")) {
return 0;
}
} catch (Exception e) {
}
try {
return jsonElement.getAsInt();
} catch (NumberFormatException e) {
throw new JsonSyntaxException(e);
}
}
}
/** * Application.java */
JsonParseUtil.setSingletonInstance(
new GsonBuilder()
.registerTypeAdapter(Integer.class,new IntegerGsonDeserializer())
.registerTypeAdapter(int.class,new IntegerGsonDeserializer())
.create());
情景2-泛型Data无数据时,返回空字符串”“
后台返回如下:
{ "code":1 ,"Data":"" }
这是由于泛型Data实际上是一个Json对象或者数组,而在Json里面空字符串不属于Json对象导致的解析出错,所以返回的Data必须是{}或者[]。
这个问题,在Gson中,我们同样通过实现JsonDeserializer接口,自定义反序列化操作来解决。
/** * HttpResultGsonDeserializer.java */
public class HttpResultGsonDeserializer implements JsonDeserializer<HttpResult<?>> {
@Override
public HttpResult deserialize(JsonElement jsonElement,JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
final JsonObject jsonObject = jsonElement.getAsJsonObject();
JsonElement jsonData = jsonObject.has("Data") ? jsonObject.get("Data") : null;
HttpResult httpResult = new HttpResult();
httpResult.setReturnCode(jsonObject.has("code") ? jsonObject.get("code").getAsInt() : 0);
httpResult.setReturnMessage(jsonObject.has("msg") ? jsonObject.get("msg").getAsString() : "");
//处理Data空串情况
if(jsonData != null && jsonData.isJsonObject()) {
//指定泛型具体类型
if (type instanceof ParameterizedType) {
Type itemType = ((ParameterizedType) type).getActualTypeArguments()[0];
Object item = jsonDeserializationContext.deserialize(jsonData,itemType);
httpResult.setData(item);
}else{
//未指定泛型具体类型
httpResult.setData(jsonData);
}
}else {
httpResult.setData(null);
}
return httpResult;
}
}
在FastJson中,也提供了相应的接口ObjectDeserializer,可以给我们自定义反序列化操作。
/** * HttpResultFJsonDeserializer.java */
public class HttpResultFJsonDeserializer implements ObjectDeserializer {
@Override
public <T> T deserialze(DefaultJSONParser parser,Object fieldName) {
Map map = parser.parSEObject(Map.class);
HttpResult httpResult = new HttpResult();
httpResult.setReturnCode(map.containsKey("code") ? (int)map.get("code") : 0);
httpResult.setReturnMessage(map.containsKey("msg") ? String.valueOf(map.get("msg")) : "");
//处理Data空串情况
if(map.containsKey("Data") && !(map.get("Data") instanceof String)) {
//指定泛型具体类型
if (type instanceof ParameterizedType) {
Type itemType = ((ParameterizedType) type).getActualTypeArguments()[0];
Object item = JSON.parSEObject(String.valueOf(map.get("Data")),itemType);
httpResult.setData(item);
}else{
//未指定泛型具体类型
httpResult.setData(map.get("Data"));
}
}else {
httpResult.setData(null);
}
return (T) httpResult;
}
@Override
public int getFastMatchToken() {
return 0;
}
}
同样,我们在Application里面进行注册。
Gson注册方式
/** * Application.java */
JsonParseUtil.setSingletonInstance(
new GsonBuilder()
.registerTypeAdapter(HttpResult.class,new HttpResultGsonDeserializer())
.create());
FastJson的注册方式
/** * Application.java */
ParserConfig.getGlobalInstance()
.putDeserializer(HttpResult.class,new HttpResultFJsonDeserializer());
Gson & FastJson 封装源码
JsonParseUtil 工具类,其中注释部分为Gson实现,可自己选择。
/** * JsonParseUtil.java */
public class JsonParseUtil {
/** * Gson 本身线程安全 * 直接采用“懒汉方式”单例写法 */
private static Gson singleton;
private static Gson getSingleton() {
if (singleton == null) {
singleton = new Gson();
}
return singleton;
}
/** * 定制Gson * 使用GsonBuilder */
public static void setSingletonInstance(@NonNull Gson gson) {
if (gson == null) {
throw new IllegalArgumentException("Gson must not be null");
}
synchronized (JsonParseUtil.class) {
if (singleton != null) {
throw new IllegalStateException("Singleton instance already exists");
}
singleton = gson;
}
}
/** * Json转Object * <p> * 如:UserInfo * * UserInfo userInfo = JsonParseUtil.parseToObject(dataStr,UserInfo.class); * * @param dataStr String */
public static <T> T parseToObject(String dataStr,Class<T> cls) {
if (TextUtils.isEmpty(dataStr)) {
return null;
}
/*T t = getSingleton().fromJson(dataStr,cls);*/
T t = JSON.parSEObject(dataStr,cls);
return t;
}
/** * Json转Object * <p> * 如:UserInfo * * UserInfo userInfo = JsonParseUtil.parseToObject(dataStr,UserInfo.class); * * 只在 GSON 中适用 * * @param jsonElement JsonElement */
public static <T> T parseToObject(Object jsonElement,Class<T> cls) {
if (jsonElement instanceof String) {
return parseToObject(jsonElement,cls);
}
if (jsonElement instanceof JsonElement && !((JsonElement)jsonElement).isJsonNull()) {
T t = getSingleton().fromJson((JsonElement)jsonElement,cls);
return t;
}
return parseToObject("",cls);
}
/** * Json转泛型Object * <p> * 如:HttpResult<UserInfo> * * HttpResult<UserInfo> tmp * = JsonParseUtil.parseToGenericObject(dataStr,new GenericType<HttpResult<UserInfo>>(){}) */
public static <T> T parseToGenericObject(String dataStr,GenericType<T> genericType) {
if (TextUtils.isEmpty(dataStr)) {
return null;
}
/*T t = getSingleton().fromJson(dataStr,genericType.getType());*/
T t = JSON.parSEObject(dataStr,genericType.getType());
return t;
}
/** * Object转Json String * <p> * 如:UserInfo */
public static String parseToJson(Object object) {
/*return (object == null) ? "" : getSingleton().toJson(object);*/
return (object == null) ? "" : JSON.toJSONString(object);
}
/** * Json转单纯的List (dataStr为一个完整Json数组) * <p> * 如:[{"code":1,"name":"小米"},{"code":2,"name":"大米"}] --> List<GoodInfo> */
public static <T> List<T> parseToPureList(String dataStr,Class<T> cls) {
/*if (TextUtils.isEmpty(dataStr)) { return null; } T[] array = getSingleton().fromJson(dataStr,cls); return new ArrayList<>(Arrays.asList(array));*/
List<T> list = JSON.parseArray(dataStr,cls);
return list;
}
/** * Json转指定Class数组 内涵不确定字段名Json数组 * <p> * 如: { "XXX":[{"Id":1352}] } --> List<YYY> */
public static <T> List<T> parseToDynamicList(String dataStr,String fieldName,Class<T> cls) {
String listData = "";
if (!TextUtils.isEmpty(dataStr)) {
try {
JSONObject jsonObj = new JSONObject(dataStr);
listData = jsonObj.optString(fieldName);
} catch (JSONException exp) {
}
}
return parseToPureList(listData,cls);
}
/** * Json转指定Class 内涵不确定字段名Json对象 * <p> * 如: { "XXX":{"Id":1352} } --> YYY */
public static <T> T parseToDynamicObject(String dataStr,Class<T> cls) {
String objectData = "";
if (!TextUtils.isEmpty(dataStr)) {
try {
JSONObject jsonObj = new JSONObject(dataStr);
objectData = jsonObj.optString(fieldName);
} catch (JSONException exp) {
}
}
return parseToObject(objectData,cls);
}
}
GenericType,上面提到的统一获取泛型具体类型。
/** * GenericType.java */
public class GenericType<T> {
private final Type type;
protected GenericType(){
Type superClass = getClass().getGenericSuperclass();
type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
public Type getType() {
return type;
}
}
最后附上一个JsonParseUtil对HttpResult的使用姿势,其他简单的就自己领悟了。
- 方式1:直接指定泛型具体类型(Gson、FastJson都一样)
HttpResult<UserInfo> httpResult = JsonParseUtil.parseToGenericObject(response,new GenericType<HttpResult<UserInfo>>(){});
- 方式2:不指定泛型具体类型
HttpResult httpResult = JsonParseUtil.parseToObject(response,HttpResult.class);
if (httpResult != null && httpResult.getData() != null) {
UserInfo userInfo;
//注意这里两个JsonParseUtil.parseToObject(...)方法是不一样的
//FastJson 调用方式
userInfo = JsonParseUtil.parseToObject(httpResult.getData().toString(),UserInfo.class);
//Gson 调用方式
userInfo = JsonParseUtil.parseToObject(httpResult.getData(),UserInfo.class);
}