關於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 實例話,在去獲取裏面的值,最後進行插入。而沒有進行嚴格的類型校驗。
這個做法可以說是比較靈活,但是實際使用過程中,因爲定義的不規範可能又會引發其他更可怕的問題。