Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> Android FastJson與不規范JSON引發的血案

Android FastJson與不規范JSON引發的血案

編輯:關於android開發

Android FastJson與不規范JSON引發的血案


去公司實習了,沒多少時間更博客了,距離上一篇博客也有一個來月了。看標題,應該可以看出,這篇文章是講一個坑,以及如何填坑。
坑是什麼?有兩個坑,其一是fastjson的bug,其二是不規范的json字符串。如何填坑,不要著急,後文詳細說明。

首先,我們看一個json字符串

{
    "doubleParam": 4.875, 
    "floatParam": 2.76,
    "extra": {
        "doubleParam": 12.23, 
        "floatParam": 12.54
    }, 
}

這是一個比較規范的json字符串,如果我們使用fastjson解析它,基本上是不會有任何問題的。

解析之前,我們需要根據這個json字符串生成bean,當然你可以選擇Android Studio中的插件Gson Formattor自動生成bean。最終的兩個bean如下所示,為了代碼簡單,我直接將屬性設為了public,省略了getter和setter方法。

public class Extra {
    public double doubleParam;
    public float floatParam;
    @Override
    public String toString() {
        return "Extra{" +
                "doubleParam=" + doubleParam +
                ", floatParam=" + floatParam +
                '}';
    }
}

public class Bean {
    public double doubleParam;
    public float floatParam;
    public Extra extra;

    @Override
    public String toString() {
        return "Bean{" +
                "doubleParam=" + doubleParam +
                ", floatParam=" + floatParam +
                ", extra=" + extra +
                '}';
    }
}

然後我們需要一個泛型的Json工具類,對於這個工具類,你要做的就是直接使用即可。

public class JsonUtil {
    /**
     * 將對象轉換成json
     *
     * @param src 對象
     * @return 返回json字符串
     * @throws Exception
     */
    public static  String toJson(T src) {

        try {
            return src instanceof String ? (String) src : JSON.toJSONString(src);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 將json通過類型轉換成對象
     *
     * @param json  json字符串
     * @param clazz 泛型類型
     * @return 返回對象, 失敗返回NULL
     */
    public static  T fromJson(String json, Class clazz) {

        try {
            return clazz.equals(String.class) ? (T) json : JSON.parseObject(json, clazz);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 將json通過類型轉換成對象
     *
     * @param json          json字符串
     * @param typeReference 引用類型
     * @return 返回對象
     */
    public static  T fromJson(String json, TypeReference typeReference) {

        try {
            return (T) (typeReference.getType().equals(String.class) ? json
                    : JSON.parseObject(json, typeReference));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

我們將這個json字符串定義成一個常量,方便使用。

public class Constant {
    public static final String JSON = "{\"doubleParam\":4.875,\"extra\":{\"doubleParam\":12.23,\"floatParam\":12.54},\"floatParam\":2.76}";
}

接下來就是開始解析了,直接使用JsonUtil工具類進行解析即可。

Bean bean = JsonUtil.fromJson(Constant.JSON, Bean.class);
System.out.println(bean);

只要能夠正常的輸出結果,基本上就是解析成功了。輸出結果如下

Bean{doubleParam=4.875, floatParam=2.76, extra=Extra{doubleParam=12.23, floatParam=12.54}}

以上過程,看上去十分完美,但是現實往往是殘酷的。第一個坑即將來臨。客戶端使用的json字符串往往是來自服務器端,而客戶端與服務器端的json規范有時候往往沒有統一,導致了一方必然需要兼容另一方。比如,服務器端返回的json字符串,無論是否是字符串類型,有時候都會加上一個引號,即使你的屬性值是int,float,double,boolean類型,服務器端給你返回的也是一個字符串。 就像下面這樣

{
    "doubleParam": "4.875", 
    "extra": {
        "doubleParam": "12.23", 
        "floatParam": "12.54"
    }, 
    "floatParam": "2.76"
}

上面這個json字符串其實是不規范的,因為不論什麼數據類型,它都加上了引號,好在使用fastjson解析這樣的數據時,也是可以正常解析的。而且解析結果十分完美,沒有一絲的錯誤。那麼為什麼說是個坑呢。

服務器返回上面這種都是字符串類型的其實都好說,不會對解析結果造成任何影響,但是往往現實也不是這樣的,有時候某一個值沒有,服務器理論上應該返回一個默認值,比如int類型應該返回”0”,然而,實際上你接受到的json字符串卻是這樣的。

{
    "doubleParam": "", 
    "extra": {
        "doubleParam": "12.23", 
        "floatParam": ""
    }, 
    "floatParam": "2.76"
}

可以看到,最外層的doubleParam值為空字符串,內部的extra對象中的floatParam值也為空字符串。其實即使是空,它們也應該被賦值一個默認值0,這樣,在客戶端解析時不會發生任何錯誤。問題就出在這個空字符串。一旦使用了空字符串,在客戶端解析時就會引發致命的錯誤。而這個錯誤的原因來自fastjson的一個Bug,然而我並不知道作者為什麼沒有修復這個bug。現在我們來解析一下,看看會發生什麼錯誤。代碼還是原來的配方。但是結果卻發生了翻天覆地的變化。程序報了一個錯誤,並且最終我們解析得到的bean是一個null。

com.alibaba.fastjson.JSONException: set property error, doubleParam
    at com.alibaba.fastjson.parser.deserializer.FieldDeserializer.setValue(FieldDeserializer.java:97)
    at com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer.parseField(DefaultFieldDeserializer.java:43)
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseField(JavaBeanDeserializer.java:400)
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:310)
    at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:115)
    at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:548)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:215)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:191)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:150)
    at com.alibaba.fastjson.JSON.parseObject(JSON.java:255)
    at cn.edu.zafu.demo.fastjson.JsonUtil.fromJson(JsonUtil.java:66)
    at cn.edu.zafu.demo.Main.main(Main.java:9)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.lang.IllegalArgumentException: Can not set double field cn.edu.zafu.demo.bean.Bean.doubleParam to null value
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
    at sun.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
    at sun.reflect.UnsafeDoubleFieldAccessorImpl.set(UnsafeDoubleFieldAccessorImpl.java:80)
    at java.lang.reflect.Field.set(Field.java:764)
    at com.alibaba.fastjson.parser.deserializer.FieldDeserializer.setValue(FieldDeserializer.java:95)
    ... 16 more
null

發生錯誤的原因這裡不追究,如果你想知道原因,跟進源碼查看一下你就會知道為什麼會產生這個異常。當前,我使用的fastjson的版本是

compile 'com.alibaba:fastjson:1.1.46.android'

應該是android中的最新版,而且我也查過了官方的wiki中的release日志,在1.1.45版本裡面明確說明了修復過這個bug,但是實際上還是存在。

修復數值類型byte、short、int、long、float、double、boolean的值為null時,反序列化失敗的問題。

確實,其他數據類型,比如int,long,boolean是不在在這個問題的,唯獨float和double存在這個問題。於是我們需要對症下藥。

我們來翻翻fastjson的源碼。在parser包下面有一個ParserConfig,裡面有這麼一段代碼。

public FieldDeserializer createFieldDeserializer(ParserConfig mapping, Class clazz, FieldInfo fieldInfo) {
        Class fieldClass = fieldInfo.getFieldClass();

        if (fieldClass == boolean.class || fieldClass == Boolean.class) {
            return new BooleanFieldDeserializer(mapping, clazz, fieldInfo);
        }

        if (fieldClass == int.class || fieldClass == Integer.class) {
            return new IntegerFieldDeserializer(mapping, clazz, fieldInfo);
        }

        if (fieldClass == long.class || fieldClass == Long.class) {
            return new LongFieldDeserializer(mapping, clazz, fieldInfo);
        }

        if (fieldClass == String.class) {
            return new StringFieldDeserializer(mapping, clazz, fieldInfo);
        }

        if (fieldClass == List.class || fieldClass == ArrayList.class) {
            return new ArrayListTypeFieldDeserializer(mapping, clazz, fieldInfo);
        }

        return new DefaultFieldDeserializer(mapping, clazz, fieldInfo);
    }

可以看到,boolean,int,long都設置了反序列化用的類,而float和double卻沒有,於是,我們就需要對這個方法進行改造。我們新建一個PatchParserConfig類繼承ParserConfig類。我們預料的結果應該是這樣的。

public class PatchParserConfig extends ParserConfig {

    @Override
    public FieldDeserializer createFieldDeserializer(ParserConfig mapping, Class clazz, FieldInfo fieldInfo) {
        Class fieldClass = fieldInfo.getFieldClass();

        if (fieldClass == boolean.class || fieldClass == Boolean.class) {
            return new BooleanFieldDeserializer(mapping, clazz, fieldInfo);
        }

        if (fieldClass == int.class || fieldClass == Integer.class) {
            return new IntegerFieldDeserializer(mapping, clazz, fieldInfo);
        }

        if (fieldClass == long.class || fieldClass == Long.class) {
            return new LongFieldDeserializer(mapping, clazz, fieldInfo);
        }

        // patch
        if (fieldClass == float.class || fieldClass == Float.class) {
            return new FloatFieldDeserializer(mapping, clazz, fieldInfo);
        }

        if (fieldClass == double.class || fieldClass == Double.class) {
            return new DoubleFieldDeserializer(mapping, clazz, fieldInfo);
        }
        // patch

        if (fieldClass == String.class) {
            return new StringFieldDeserializer(mapping, clazz, fieldInfo);
        }

        if (fieldClass == List.class || fieldClass == ArrayList.class) {
            return new ArrayListTypeFieldDeserializer(mapping, clazz, fieldInfo);
        }

        return new DefaultFieldDeserializer(mapping, clazz, fieldInfo);
    }
}

很明顯的我們增加了下面的幾行代碼

// patch
        if (fieldClass == float.class || fieldClass == Float.class) {
            return new FloatFieldDeserializer(mapping, clazz, fieldInfo);
        }

        if (fieldClass == double.class || fieldClass == Double.class) {
            return new DoubleFieldDeserializer(mapping, clazz, fieldInfo);
        }
        // patch

而FloatFieldDeserializer類和DoubleFieldDeserializer都是哪來的呢。這個很簡單,照樣畫葫蘆就可以了,在parser包裡的deserializer包種有各種反序列化用的類。我們選擇其中一個照樣畫葫蘆就ok了。比如我們選擇LongFieldDeserializer,其代碼如下

public class LongFieldDeserializer extends FieldDeserializer {

    private final ObjectDeserializer fieldValueDeserilizer;

    public LongFieldDeserializer(ParserConfig mapping, Class clazz, FieldInfo fieldInfo){
        super(clazz, fieldInfo);

        fieldValueDeserilizer = mapping.getDeserializer(fieldInfo);
    }

    @Override
    public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map fieldValues) {
        Long value;

        final JSONLexer lexer = parser.getLexer();
        if (lexer.token() == JSONToken.LITERAL_INT) {
            long val = lexer.longValue();
            lexer.nextToken(JSONToken.COMMA);
            if (object == null) {
                fieldValues.put(fieldInfo.getName(), val);
            } else {
                setValue(object, val);
            }
            return;
        } else if (lexer.token() == JSONToken.NULL) {
            value = null;
            lexer.nextToken(JSONToken.COMMA);

        } else {
            Object obj = parser.parse();

            value = TypeUtils.castToLong(obj);
        }

        if (value == null && getFieldClass() == long.class) {
            // skip
            return;
        }

        if (object == null) {
            fieldValues.put(fieldInfo.getName(), value);
        } else {
            setValue(object, value);
        }
    }

    public int getFastMatchToken() {
        return fieldValueDeserilizer.getFastMatchToken();
    }
}

按照這個模子,很容易的就出來了我們的兩個類。

public class DoubleFieldDeserializer extends FieldDeserializer {

    private final ObjectDeserializer fieldValueDeserilizer;

    public DoubleFieldDeserializer(ParserConfig mapping, Class clazz, FieldInfo fieldInfo) {
        super(clazz, fieldInfo);

        fieldValueDeserilizer = mapping.getDeserializer(fieldInfo);
    }

    @Override
    public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map fieldValues) {
        Double value;

        final JSONLexer lexer = parser.getLexer();
        if (lexer.token() == JSONToken.LITERAL_INT) {
            long val = lexer.longValue();
            lexer.nextToken(JSONToken.COMMA);
            if (object == null) {
                fieldValues.put(fieldInfo.getName(), val);
            } else {
                setValue(object, val);
            }
            return;
        } else if (lexer.token() == JSONToken.NULL) {
            value = null;
            lexer.nextToken(JSONToken.COMMA);

        } else {
            Object obj = parser.parse();

            value = TypeUtils.castToDouble(obj);
        }

        if (value == null && getFieldClass() == double.class) {
            // skip
            return;
        }

        if (object == null) {
            fieldValues.put(fieldInfo.getName(), value);
        } else {
            setValue(object, value);
        }
    }

    @Override
    public int getFastMatchToken() {
        return fieldValueDeserilizer.getFastMatchToken();
    }
}
public class FloatFieldDeserializer extends FieldDeserializer {

    private final ObjectDeserializer fieldValueDeserilizer;

    public FloatFieldDeserializer(ParserConfig mapping, Class clazz, FieldInfo fieldInfo) {
        super(clazz, fieldInfo);

        fieldValueDeserilizer = mapping.getDeserializer(fieldInfo);
    }

    @Override
    public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map fieldValues) {
        Float value;

        final JSONLexer lexer = parser.getLexer();
        if (lexer.token() == JSONToken.LITERAL_INT) {
            long val = lexer.longValue();
            lexer.nextToken(JSONToken.COMMA);
            if (object == null) {
                fieldValues.put(fieldInfo.getName(), val);
            } else {
                setValue(object, val);
            }
            return;
        } else if (lexer.token() == JSONToken.NULL) {
            value = null;
            lexer.nextToken(JSONToken.COMMA);

        } else {
            Object obj = parser.parse();

            value = TypeUtils.castToFloat(obj);
        }

        if (value == null && getFieldClass() == float.class) {
            // skip
            return;
        }

        if (object == null) {
            fieldValues.put(fieldInfo.getName(), value);
        } else {
            setValue(object, value);
        }
    }

    @Override
    public int getFastMatchToken() {
        return fieldValueDeserilizer.getFastMatchToken();
    }

萬事具備,只欠東風,那麼我們如何將我們的PatchParserConfig注入到解析的過程中去呢。我們使用android studio的快捷鍵command+左鍵一直跟進源碼,發現JsonUtil工具類中的兩個fromJson方法,最終會調用下面的兩個方法

public static final  T parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor,
                                          int featureValues, Feature... features) {
        if (input == null) {
            return null;
        }

        for (Feature featrue : features) {
            featureValues = Feature.config(featureValues, featrue, true);
        }

        DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);

        if (processor instanceof ExtraTypeProvider) {
            parser.getExtraTypeProviders().add((ExtraTypeProvider) processor);
        }

        if (processor instanceof ExtraProcessor) {
            parser.getExtraProcessors().add((ExtraProcessor) processor);
        }

        T value = (T) parser.parseObject(clazz);

        parser.handleResovleTask(value);

        parser.close();

        return (T) value;
    }

而該函數中新建的DefaultJSONParser對象內部維持了ParserConfig的一個引用,我們可以通過setter方法修改該引用為我們自己的的PatchParserConfig,於是一切都變得如此簡單。

 private static final ParserConfig PARSER_CONFIG = new PatchParserConfig();
 private static final  T parseObject(String input, Type clazz, ParserConfig config, ParseProcess processor,
                                           int featureValues, Feature... features) {
        if (input == null) {
            return null;
        }

        for (Feature featrue : features) {
            featureValues = Feature.config(featureValues, featrue, true);
        }

        DefaultJSONParser parser = new DefaultJSONParser(input, config, featureValues);
        parser.setConfig(PARSER_CONFIG);
        if (processor instanceof ExtraTypeProvider) {
            parser.getExtraTypeProviders().add((ExtraTypeProvider) processor);
        }

        if (processor instanceof ExtraProcessor) {
            parser.getExtraProcessors().add((ExtraProcessor) processor);
        }

        T value = (T) parser.parseObject(clazz);

        parser.handleResovleTask(value);

        parser.close();

        return (T) value;
    }

為了更加方便使用,我們在這個方法基礎上再分裝兩個類似JSON類中的參數少的方法。

private static final  T parseObject(String text, Class clazz) {
        return (T) parseObject(text, (Type) clazz, PatchParserConfig.getGlobalInstance(),null,JSON.DEFAULT_PARSER_FEATURE, new Feature[0]);
    }


    private static final  T parseObject(String text, TypeReference type, Feature... features) {
        return (T) parseObject(text, type.getType(), PatchParserConfig.getGlobalInstance(), null, JSON.DEFAULT_PARSER_FEATURE, features);
    }

接著改造我們原來JsonUtil類中的fromJson的兩個方法,將其調用執行自己內部的靜態函數parserObject,如下。

  public static  T fromJson(String json, Class clazz) {

        try {
            return clazz.equals(String.class) ? (T) json : parseObject(json, clazz);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

    public static  T fromJson(String json, TypeReference typeReference) {

        try {
            return (T) (typeReference.getType().equals(String.class) ? json
                    : parseObject(json, typeReference));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

最終,暴露給客戶端的就是這兩個方法。我們一個狸貓換太子,一下子就對服務器返回的空字符串進行了兼容。下面測試一下能否正常反序列化。輸出結果如下

Bean{doubleParam=0.0, floatParam=2.76, extra=Extra{doubleParam=12.23, floatParam=0.0}}

異常消失了,世界都安靜了。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved