Gson&FastJson解析异常Json的处理

xiaoxiao2021-02-28  83

这几天因为开发需求对项目中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 , "msg":"成功" , "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 , "msg":"成功" , "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 , "msg":"成功" , "Data":"" }

这是由于泛型Data实际上是一个Json对象或者数组,而在Json里面空字符串不属于Json对象导致的解析出错,所以返回的Data必须是{}或者[]。 这个问题,在Gson中,我们同样通过实现JsonDeserializer接口,自定义反序列化操作来解决。

/** * HttpResultGsonDeserializer.java */ public class HttpResultGsonDeserializer implements JsonDeserializer<HttpResult<?>> { @Override public HttpResult deserialize(JsonElement jsonElement, Type type, 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, Type type, 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, String fieldName, 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); }
转载请注明原文地址: https://www.6miu.com/read-33648.html

最新回复(0)