这几天因为开发需求对项目中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);
    }