關於Fastjson 數組解析異常問題的排查

關於Fastjson 數組解析異常問題的排查

今日在使用字符串轉json的時候,遇到問題,發現轉換失敗,
報錯日誌如下:

com.alibaba.fastjson.JSONException: expect '[', but string, pos 8, line 1, column 9"G2京滬高速"
	at com.alibaba.fastjson.util.TypeUtils.castToJavaBean(TypeUtils.java:1366)
	at com.alibaba.fastjson.util.TypeUtils.cast(TypeUtils.java:932)
	at com.alibaba.fastjson.JSONArray.toJavaList(JSONArray.java:450)
	at com.hysm.service.impl.OtaProviderServiceImpl.getRoutePriceAndPath(OtaProviderServiceImpl.java:772)
	at com.hysm.service.impl.OtaProviderServiceImpl.getResultMessage(OtaProviderServiceImpl.java:187)
	at sun.reflect.GeneratedMethodAccessor79.invoke(Unknown Source)

遂緊急排查問題,定位到報錯代碼如下:

JSONArray paths = json.getJSONObject("route").getJSONArray("paths");
List<Paths>     pathsList = paths.toJavaList(Paths.class);

將原始字符串獲取下來,線下測試確實無法轉換。但是經過測試 發現 原來是獲取高德路徑規劃接口的時候,高德返回參數值不規範。
接口連接:https://restapi.amap.com/v3/direction/driving
此接口的 toll_roal 如果爲空則返回列表,有值則返回字符串。
大廠神奇的規範不深做吐槽。

接下來有發現一個有意思的事情:

JSONArray paths = json.getJSONObject("route").getJSONArray("paths");
 List<Paths> pathsList = JSON.parseArray(paths.toJSONString(),Paths.class);

此代碼又可以正常運行!! why?
都是fastjson提供的方法,爲什麼會形成差異。

帶着問題進入源碼,先看 JSONArray.toJavaList。

public <T> List<T> toJavaList(Class<T> clazz) {
        List<T> list = new ArrayList(this.size());
        ParserConfig config = ParserConfig.getGlobalInstance();
        Iterator var4 = this.iterator();

        while(var4.hasNext()) {
            Object item = var4.next();
            T classItem = TypeUtils.cast(item, clazz, config);
            list.add(classItem);
        }

        return list;
    }

關鍵代碼是裏面的 TypeUtils.cast()。
TypeUtils的代碼比較醜,基本就是根據字段類型來進行值的轉換。

public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
        if (parser.lexer.token() == 8) {
            parser.lexer.nextToken(16);
            return null;
        } else if (type == JSONArray.class) {
            JSONArray array = new JSONArray();
            parser.parseArray(array);
            return array;
        } else {
            Collection list = TypeUtils.createCollection(type);
            Type itemType = TypeUtils.getCollectionItemType(type);
            parser.parseArray(itemType, list, fieldName);
            return list;
        }
    }

關鍵的地方來了:

public void parseArray(Type type, Collection array, Object fieldName) {
        int token = this.lexer.token();
        if (token == 21 || token == 22) {
            this.lexer.nextToken();
            token = this.lexer.token();
        }

        if (token != 14) {
            throw new JSONException("expect '[', but " + JSONToken.name(token) + ", " + this.lexer.info());
        } else {
            ObjectDeserializer deserializer = null;
            if (Integer.TYPE == type) {
            

很明顯,此處的toll_road字段 因爲定義的是List 所以在經過fastjson的jsonarray的list解析器的時候,發現值類型不匹配 拋出異常。 邏輯合理。

下面來看看JSON.parseArray(paths.toJSONString(),Paths.class); 這段代碼的實現。
裏面的方法實現:

public static <T> List<T> parseArray(String text, Class<T> clazz) {
        if (text == null) {
            return null;
        } else {
            DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance());
            JSONLexer lexer = parser.lexer;
            int token = lexer.token();
            ArrayList list;
            if (token == 8) {
                lexer.nextToken();
                list = null;
            } else if (token == 20 && lexer.isBlankInput()) {
                list = null;
            } else {
                list = new ArrayList();
                parser.parseArray(clazz, list);
                parser.handleResovleTask(list);
            }

            parser.close();
            return list;
        }
    }

發現最後進入的是 JavaBeanDeserializer 這個類。和上面的解析器不同。中間代碼也是比較難看的類型比對,跳過。比對後進入ArrayListTypeFieldDeserializer。
關鍵代碼如下:

public void parseField(DefaultJSONParser parser, Object object, Type objectType, Map<String, Object> fieldValues) {
        JSONLexer lexer = parser.lexer;
        int token = lexer.token();
        if (token != 8 && (token != 4 || lexer.stringVal().length() != 0)) {
            ArrayList list = new ArrayList();
            ParseContext context = parser.getContext();
            parser.setContext(context, object, this.fieldInfo.name);
            this.parseArray(parser, objectType, list);
            parser.setContext(context);
            if (object == null) {
                fieldValues.put(this.fieldInfo.name, list);
            } else {
                this.setValue(object, list);
            }

        } else {
            this.setValue(object, (String)null);
        }
    }

進入parseArray 方法。

while(true) {
                if (lexer.isEnabled(Feature.AllowArbitraryCommas)) {
                    while(lexer.token() == 16) {
                        lexer.nextToken();
                    }
                }

                if (lexer.token() == 15) {
                    lexer.nextToken(16);
                    break;
                }

                Object val = itemTypeDeser.deserialze(parser, (Type)itemType, i);
                array.add(val);
                parser.checkListResolve(array);
                if (lexer.token() == 16) {
                    lexer.nextToken(this.itemFastMatchToken);
                }

                ++i;
            }

重點來了,上述的
Object val = itemTypeDeser.deserialze(parser, (Type)itemType, i);
array.add(val);
他是先將list 實例話,在去獲取裏面的值,最後進行插入。而沒有進行嚴格的類型校驗。
這個做法可以說是比較靈活,但是實際使用過程中,因爲定義的不規範可能又會引發其他更可怕的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章