2021SC@SDUSC
本文在个人博客同步发出,地址Redbit的个人历程
文章目录
概要
上一篇fastjson源码解析——反序列化(六)对fastjson两种parseArray
API展开了深入的对比,通过异同点分析API的设计、内部逻辑架构等。本文将继续深入探索fastjson对JSON对象数组的反序列化结构。
先考虑对于单一类型的JSON对象数组的反序列化架构
1. parseArray(Class<?>, Collection)
方法
public void parseArray(Class<?> clazz, @SuppressWarnings("rawtypes") Collection array) {
parseArray((Type) clazz, array);
}
此方法将普通类(Class<?>)的参数clazz转化为Type类型,调用了另外一个API,可以体现出fastjson内部调用的逻辑。
fastjson的开发者一直贯彻一个原则,同时注重API的易用性与内部逻辑的简单化、去冗余化。这样的结构很成熟,也是提高代码效率、代码可读性的最基本的技巧
接下来看下一个结构
2. parseArray(Type, Collection)
方法
public void parseArray(Type type, Collection array) {
parseArray(type, array, null);//null指的是fieldName
}
这个方法依旧考虑到结构性,将原来的API转化为其他API调用。
这里的null值对应的方法参数fieldName,是考虑到fastjson用户需要对对象的某些属性规定特殊的反序列化命名,例如规定将对象的cid
属性对应绑定到JSON字段中的id
域,只需要在对象cid
前加上一行Annotation
声明此规定即可。在这里,由于我们没有特殊规定反序列化需要特殊绑定的属性名,所以调用方法的传参设置为空null
。
3. parseArray(Type, Collection, Object)
方法
这个方法是单一类型对象数组反序列化具体操作的核心方法,代码规定了parseArray
API对token
的使用,对明确类型数据的反序列化(使用具体类型的反序列化实例执行),以及对非法JSON字符串出现异常的反馈。请看代码,可参考代码中穿差的注释帮助理解。
@SuppressWarnings({ "unchecked", "rawtypes" })
public void parseArray(Type type, Collection array, Object fieldName) {
int token = lexer.token(); //获取token
if (token == JSONToken.SET || token == JSONToken.TREE_SET) {
lexer.nextToken(); //如果对应位置是set系列的数据,读取下一个token
token = lexer.token();
}
if (token != JSONToken.LBRACKET) {
/** 检查token的合法性,本质上是检查传入JSON字符串的合法性
* 例如,如果字符串不包含数组的起始字符'[',该字符串数据可以被认为不是JSON数据或者数据错误
* 抛出异常,中止反序列化操作 */
throw new JSONException("field " + fieldName + " expect '[', but " + JSONToken.name(token) + ", " + lexer.info());
}
ObjectDeserializer deserializer = null;
//定义抽象的反序列化实例,用来装载其子类定义的具体反序列化实例
//通过传入的type,先快速区分出int和String这两种最常见的反序列化类型,直接调用反序列化实例
if (int.class == type) { //检查int类型数据
deserializer = IntegerCodec.instance;
lexer.nextToken(JSONToken.LITERAL_INT);
} else if (String.class == type) { //检查String字符串类型
deserializer = StringCodec.instance;
lexer.nextToken(JSONToken.LITERAL_STRING);
} else {
/** 若不是int/String类型数据,则通过配置调出该类型的反序列化实例
* 可能是fastjson内部已经注册的类型(详见parseObject介绍部分)
* 也可能是未注册的类型,需要内部先根据属性的类型,生成该类型的反序列化器,注册后返回这里的调用 */
deserializer = config.getDeserializer(type);
lexer.nextToken(deserializer.getFastMatchToken());
}
//以下代码,使用反序列化实例,开始反序列化并将结果保存在List中
ParseContext context = this.context;
this.setContext(array, fieldName);
try {
for (int i = 0;; ++i) { //对JSON字符串内某个对象的各字段反序列化
if (lexer.isEnabled(Feature.AllowArbitraryCommas)) { //检查配置特性的状态(AllowArbitraryCommas)
while (lexer.token() == JSONToken.COMMA) {
lexer.nextToken();
continue;
}
}
if (lexer.token() == JSONToken.RBRACKET) {
break; //遇到右中括号']'直接跳出循环,表示token已经遍历结束,反序列化完成
}
if (int.class == type) { //对int类型反序列化
Object val = IntegerCodec.instance.deserialze(this, null, null);
array.add(val); //添加到返回的数据内
} else if (String.class == type) { //对String反序列化
String value;
if (lexer.token() == JSONToken.LITERAL_STRING) {
value = lexer.stringVal();
lexer.nextToken(JSONToken.COMMA);
} else {
Object obj = this.parse();
if (obj == null) {
value = null;
} else {
value = obj.toString();
}
}
array.add(value);
} else { //对普通对象反序列化
Object val;
if (lexer.token() == JSONToken.NULL) {
//避开对null对象的反序列化,产生不为空的对象(产生错误)
lexer.nextToken();
val = null;
} else {
val = deserializer.deserialze(this, type, i);
}
array.add(val);
checkListResolve(array);
}
if (lexer.token() == JSONToken.COMMA) { //遇到逗号','跳转下一个token,继续循环遍历(continue)
lexer.nextToken(deserializer.getFastMatchToken());
continue;
}
}
} finally {
this.setContext(context);
}
lexer.nextToken(JSONToken.COMMA);//跳转到下一个JSON字符串的内容,让后续方法调用
}
这个方法代码较长,但是可读性很好,层次分明地规定了单一类型JSON对象数组的反序列化执行顺序。先检索token
,读取其中存放数据的类型,使用抽象的反序列化器,装载创建的针对特定数据类型的反序列化实例,利用多态的优势,给JSON数组中每一个对象执行反序列化。将数据保存在传参的Collection
中,返回原先的方法。
透过fastjson对数组的反序列化操作顺序,不难发现其实parseArray内部使用了和parseObject不同的操作逻辑(实际上是利用了对象反序列化的逻辑,调用了它的方法,循环地对每一个JSON字符串字串(代表一个JSON对象)反序列化。
由于这个API针对的是单个类型的JSON对象,所以我们只需要明确一个反序列化实例,即可使用这个实例对所有JSON对象反序列化。在执行的过程中,此API极力避开针对每个元素规定类型的parseArray
API相同的代码,尽可能以最专一的方式使用代码。
如此操作,有明显的好处。如果这两个parseArray的API存在部分相同代码,那么这段代码一定是具有普适性的,不论是对于单一类型还是规定了每个元素类型的API执行,都不是一个好事。通用的代码,要么可以提取成公用方法(这样会破坏API调用的结构),要么降低了执行效率(通用代码必然没有专用代码的特殊性强),也增加了代码冗余,降低可维护性。
因此fastjson开发者在此处尽可能设计专门针对单一类型对象的反序列化代码,降低了耦合度,也提高了效率。在需要专一时专一到极致,当需要公用代码时,又做到极大的通用性。
最后
本文介绍了针对单一类型的parseArray()
API内部逻辑,通过简单的比较,明晰了API包装、调用的结构。从最外层的parseArray(Class<?>, Collection)
开始,层层递进,查看了API封装更普适API的方式,最后详解parseArray(Type, Collection, Object)
方法,看到了fastjson如何使用token,检查数组内元素的类型,创建具体的反序列化实例(同parseObject
逻辑相同),对JSON字符串的每个元素(以COMMA,
分开)执行反序列化。
在这之中,我们学习了fastjson开发者对代码结构、冗余的巧妙处理,这些技巧都适用于我们日常代码的开发,毕竟简洁的代码、易懂的API结构是提高代码可读性的基本方法。
下一篇,我们将注重于针对不同类型,规定了每个元素类型的JSON数组反序列化APIparseArray(String, Type[])
的实现逻辑、结构,以对比的方式分析其与本文所提到的结构异同点,更好地理解难点。
感谢各位老师的阅读与指导!