fastjson源码解析——反序列化(七)

2021SC@SDUSC

本文在个人博客同步发出,地址Redbit的个人历程

概要

上一篇fastjson源码解析——反序列化(六)对fastjson两种parseArrayAPI展开了深入的对比,通过异同点分析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)方法

这个方法是单一类型对象数组反序列化具体操作的核心方法,代码规定了parseArrayAPI对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极力避开针对每个元素规定类型的parseArrayAPI相同的代码,尽可能以最专一的方式使用代码。
如此操作,有明显的好处。如果这两个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[])的实现逻辑、结构,以对比的方式分析其与本文所提到的结构异同点,更好地理解难点。
感谢各位老师的阅读与指导!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值