本文主要讲述了一个具有"随机性"的反序列化错误!
Fastjson作为一款高性能的JSON序列化框架,使用场景众多,不过也存在一些潜在的bug和不足。本文主要讲述了一个具有"随机性"的反序列化错误!
package test;
import java.util.List;
public class StewardTipItem {
private Integer type;
private List<String> contents;
public StewardTipItem(Integer type, List<String> contents) {
this.type = type;
this.contents = contents;
}
}
package test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class StewardTipCategory {
private String category;
private List<StewardTipItem> items;
public StewardTipCategory build() {
return null;
}
//C1 下文使用C1引用该构造函数
public StewardTipCategory(String category, Map<Integer, List<String>> items) {
List<StewardTipItem> categoryItems = new ArrayList<>();
for (Map.Entry<Integer, List<String>> item : items.entrySet()) {
StewardTipItem tipItem = new StewardTipItem(item.getKey(), item.getValue());
categoryItems.add(tipItem);
}
this.items = categoryItems;
this.category = category;
}
// C2 下文使用C2引用该构造函数
public StewardTipCategory(String category, List<StewardTipItem> items) {
this.category = category;
this.items = items;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public List<StewardTipItem> getItems() {
return items;
}
public void setItems(List<StewardTipItem> items) {
this.items = items;
}
}
package test;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class StewardTip {
private List<StewardTipCategory> categories;
public StewardTip(Map<String, Map<Integer, List<String>>> categories) {
List<StewardTipCategory> tipCategories = new ArrayList<>();
for (Map.Entry<String, Map<Integer, List<String>>> category : categories.entrySet()) {
StewardTipCategory tipCategory = new StewardTipCategory(category.getKey(), category.getValue());
tipCategories.add(tipCategory);
}
this.categories = tipCategories;
}
public StewardTip(List<StewardTipCategory> categories) {
this.categories = categories;
}
public List<StewardTipCategory> getCategories() {
return categories;
}
public void setCategories(List<StewardTipCategory> categories) {
this.categories = categories;
}
}
{
"categories":[
{
"category":"工艺类",
"items":[
{
"contents":[
"工艺类-提醒项-内容1",
"工艺类-提醒项-内容2"
],
"type":1
},
{
"contents":[
"工艺类-疑问项-内容1"
],
"type":2
}
]
}
]
}
package test;
import com.alibaba.fastjson.JSONObject;
public class FastJSONTest {
public static void main(String[] args) {
String tip = "{\"categories\":[{\"category\":\"工艺类\",\"items\":[{\"contents\":[\"工艺类-提醒项-内容1\",\"工艺类-提醒项-内容2\"],\"type\":1},{\"contents\":[\"工艺类-疑问项-内容1\"],\"type\":2}]}]}";
try {
JSONObject.parseObject(tip, StewardTip.class);
} catch (Exception e) {
e.printStackTrace();
}
}
}
当执行FastJSONTest的main方法时报错:
com.alibaba.fastjson.JSONException: syntax error, expect {, actual [
at com.alibaba.fastjson.parser.deserializer.MapDeserializer.parseMap(MapDeserializer.java:228)
at com.alibaba.fastjson.parser.deserializer.MapDeserializer.deserialze(MapDeserializer.java:67)
at com.alibaba.fastjson.parser.deserializer.MapDeserializer.deserialze(MapDeserializer.java:43)
at com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer.parseField(DefaultFieldDeserializer.java:85)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:838)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:288)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:284)
at com.alibaba.fastjson.parser.deserializer.ArrayListTypeFieldDeserializer.parseArray(ArrayListTypeFieldDeserializer.java:181)
at com.alibaba.fastjson.parser.deserializer.ArrayListTypeFieldDeserializer.parseField(ArrayListTypeFieldDeserializer.java:69)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:838)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:288)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:672)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:396)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:300)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:573)
at test.FastJSONTest.main(FastJSONTest.java:17)
参考java.lang.Class#getDeclaredConstructors的注释,可得到A1:
package test;
import com.alibaba.fastjson.JSONObject;
import java.lang.reflect.Constructor;
public class FastJSONTest {
public static void main(String[] args) {
Constructor<?>[] declaredConstructors = StewardTipCategory.class.getDeclaredConstructors();
// if true must fail!
if ("public test.StewardTipCategory(java.lang.String,java.util.Map<java.lang.Integer, java.util.List<java.lang.String>>)".equals(declaredConstructors[0].toGenericString())) {
String tip = "{\"categories\":[{\"category\":\"工艺类\",\"items\":[{\"contents\":[\"工艺类-提醒项-内容1\",\"工艺类-提醒项-内容2\"],\"type\":1},{\"contents\":[\"工艺类-疑问项-内容1\"],\"type\":2}]}]}";
try {
JSONObject.parseObject(tip, StewardTip.class);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
StewardTipCategory构造函数C1方法签名明显不是一个很好的选择,方法体除了属性赋值,还做了一些额外的类型/数据转换,也应该尽量避免。
开发人员对于使用的技术与框架要有深入的研究,尤其是底层原理,不能停留在使用层面。一些不起眼的事情可能导致不可思议的问题:java.lang.Class#getDeclaredConstructors。
框架实现时要保持严谨,报错信息尽可能清晰明了,StewardTipCategory反序列化失败的原因在于,fastjson只检验了属性名称,构造函数参数个数而没有进一步校验属性类型。
<<重构:改善既有代码的设计>>提倡代码方法块尽量短小精悍,Fastjson某些模块的方法过于臃肿。