今日在使用字符串转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 实例话,在去获取里面的值,最后进行插入。而没有进行严格的类型校验。
这个做法可以说是比较灵活,但是实际使用过程中,因为定义的不规范可能又会引发其他更可怕的问题。