Fastjson核心解析器DefaultJSONParser,解析算法递归下降算法,实例解析json的步骤

news2024/10/7 10:15:27

先恭喜热火没有在3-0的情况下被凯尔特人翻盘,抢七获胜成功晋级总决赛~

最近的项目用到了fastjson,因为源码比较容易搞到,所以就拿来简单的了解了一下,json的主要功能就是解析json和生成json字符串,今天主要是从解析json的方向先来入手。

fastjson里面的核心解析器是:DefaultJSONParser,其主要作用就是将JSON格式的字符串解析为java对象。DefaultJSONParser的解析算法基于递归下降算法。

本文主要就是详细了解一下这个递归下降算法。

目录

递归下降算法

产生式

相关概念

fastjson中的递归下降

DefaultJSONParser源码

初始状态:读取到 '{',进入parseObject方法

parseObject方法:创建JSONObject对象,读取到 "name":"Tom"

读取到 "address": {,递归到parseObject方法

parseObject方法:创建JSONObject对象,读取到 "province": "Guangdong",}

读取到 '}',JSON解析完成。

总结


递归下降算法

递归下降算法是一种基于产生式的自顶向下的语法分析方法,即从文法的起始符号开始,按照产生式展开,直到匹配完整个输入串。

产生式

产生式是一种知识表达方法,是事实与规则的表示,如:“老李年龄是35岁”,可以写成产生式:

{
    "name":"老李",
    "age":35
}

相关概念

递归下降算法的主要思想是,将文法的每个非终结符看作一个函数,每个函数对应一个产生式,函数的任务是把输入串匹配到该产生式的右部。在函数内部,可以再次调用其他函数,以实现对子表达式的递归分析。如:

<trem> ::= <expr>+<term> | <expr>-<term> | <term> <expr> ::= x

被<>括起来的就是非终结符,因为其可以被::=右侧的式子代替。

“|”表示选择,意思就是<term>可以被<expr>+<term>代替,也可以被<expr>-<term>代替,还可以被<term>代替。

没有出现在::=左侧的就被称作终结符,比如“x”,“+”,“-”。

递归下降算法通常需要先对文法进行一些处理,例如,将左递归的产生式转化为右递归的产生式。

如果语法中有产生式,A→A|称为左递归产生式。因为每一次解析A的时候,会碰到A可以代替A的情况,这样就会无限的递归下去。AAAAAAAAAAAAA

fastjson中不需要担心,因为json文法属于LL(1)文法,

LL(1)文法的解释是:

L:自顶向下分析是从左向右扫描输入串。

L:分析过程中将用到最左推导。

1:表明只需要向右看一个符号便可决定如何推导。

所以json里面不会出现左递归的情况。

fastjson中的递归下降

当DefaultJSONParser接收到一个JSON字符串时,它首先会将其转化为一个Token流。例如,对于以下JSON字符串:

{ "name": "Tom", "address": { "province": "Guangdong", } }

DefaultJSONParser会将其转化为如下的Token流:

{, name, :, Tom, ,, address, :, {, province, :, Guangdong, , , }, }

接下来,DefaultJSONParser会根据Token流的类型,逐一解析出相应的Java对象。例如,在上述JSON字符串中,DefaultJSONParser会先解析出一个JSONObject,然后再解析出其中的每个键值对。

DefaultJSONParser源码

 

主要看的是fastjson中的DefaultJSONParser的parse方法,方法签名如下所示:

public Object parse()

该方法会调用另外一个重载方法public Object parse(Object fieldName)

该方法的主要代码如下所示:

public Object parse(Object fieldName) {
        final JSONLexer lexer = this.lexer;
        switch (lexer.token()) {
            //中间基本都是case,为了不影响篇幅,先省略
            ... ...
        }
}

用一个例子来讲一下源代码,现在有一个json串是这样的:

{
  "name": "Tom",
  "address": {
    "province": "Guangdong",
  }
}

1.初始状态:读取到 '{',进入parseObject方法

//上面那个LBRACE是JSONToken中的常量12,代表的就是符号"{",是大部分json字符串的初始字符。    
//public final static int LBRACE = 12; // ("{"),
case LBRACE:
    Map object = isEnabled(Feature.UseNativeJavaObject)
        ? lexer.isEnabled(Feature.OrderedField)
        ? new HashMap()
        : new LinkedHashMap()
        : new JSONObject(lexer.isEnabled(Feature.OrderedField));
    return parseObject(object, fieldName);
    
//在fastjson中,UseNativeJavaObject是一个配置选项,
//用于在JSON与Java对象之间进行转换时控制是否使用Java原生对象。
//也就是使用HashMap代替JSONObject,使用ArrayList代替JSONArray
//默认是false
isEnabled(Feature.UseNativeJavaObject) 没有配置的情况下会是false

//属性是否是有序的
//默认是false,也就是JSONObject里面是一个HashMap
//如果是ture的话,里面会是一个LinkedHashMap
lexer.isEnabled(Feature.OrderedField)

//最后获取一个JSONObject传入了parseObject方法里面
parseObject(object, fieldName);

2.parseObject方法:创建JSONObject对象,读取到 "name":"Tom"

public final Object parseObject(final Map object, Object fieldName) {
    final JSONLexer lexer = this.lexer;

    //判断token是否为空,咱们目前代码,token是符号“{”
    if (lexer.token() == JSONToken.NULL) {
        lexer.nextToken();
        return null;
    }

    //判断符号是否是“}”
    if (lexer.token() == JSONToken.RBRACE) {
        lexer.nextToken();
        return object;
    }

    //判断是否是String字符串,并且长度是0
    if (lexer.token() == JSONToken.LITERAL_STRING && lexer.stringVal().length() == 0) {
        lexer.nextToken();
        return object;
    }

    //判断token不是“{”,也不是“,”
    if (lexer.token() != JSONToken.LBRACE && lexer.token() != JSONToken.COMMA) {
        throw new JSONException("syntax error, expect {, actual " + lexer.tokenName() + ", " + lexer.info());
    }
    
    //解析时的上下文对象
    ParseContext context = this.context;
    try {
    //通过第一步知道,这个表达式结果为true
        boolean isJsonObjectMap = object instanceof JSONObject;
        //最终的map
        Map map = isJsonObjectMap ? ((JSONObject) object).getInnerMap() : object;
        boolean setContextFlag = false;
        
        for (;;) {
            //跳过字符串的空格换行等空白部分
            lexer.skipWhitespace();
            
            //获取当前的字符,当前字符应该是“"”
            char ch = lexer.getCurrent();
            
            //这一段代码时去掉多余的“,”号,比如{"a":1,,,"b":2}中间有两个逗号是多余的
            //但是目前咱们的符号是“{”,所以在这一步会跳过该段代码
            if (lexer.isEnabled(Feature.AllowArbitraryCommas)) {
                while (ch == ',') {
                    lexer.next();
                    lexer.skipWhitespace();
                    ch = lexer.getCurrent();
                }
            }
            
            //接下来这一段是获取key值的
            boolean isObjectKey = false;
            Object key;
            if (ch == '"') {
            //扫描key值,获取到key值“name”
                key = lexer.scanSymbol(symbolTable, '"');
                //跳过空格
                lexer.skipWhitespace();
                //当前的字符应该是":"
                ch = lexer.getCurrent();
                 if (ch != ':') {
                    throw new JSONException("expect ':' at " + lexer.pos() + ", name " + key);
                }
            }
            ... ...
            
            //获取到下一个token
            if (!isObjectKey) {
                lexer.next();
                lexer.skipWhitespace();
            }
            
            //获取当前字符串,是“"”
            ch = lexer.getCurrent();

            lexer.resetStringPosition();
            
            ... ...
            
            //设置上下文,目前为止上下文应该还是null,啥都没有
            if (!setContextFlag) {
                if (this.context != null && fieldName == this.context.fieldName && object == this.context.object) {
                    context = this.context;
                } else {
                    //上下文会有一个初始的上下文,里面没有元素
                    ParseContext contextR = setContext(object, fieldName);
                    if (context == null) {
                        context = contextR;
                    }
                    setContextFlag = true;
                }
            }
            
            ......
                       
            Object value;
            if (ch == '"') {
                //扫描字符串,获取value值“Tom”
                lexer.scanString();
                String strValue = lexer.stringVal();
                value = strValue;
            
                ... ...
            
                //将key值和value值插入进map
                map.put(key, value);
            }
            
            ... ...
            
            //获取到下逗号,进行下一次循环获取下一个映射
            lexer.skipWhitespace();
            ch = lexer.getCurrent();
            if (ch == ',') {
                lexer.next();
                continue;
            }

3.读取到 "address": {,递归到parseObject方法

这一步在获取value值之前,和上一步都是一样的。获取value值的分支如下:

else if (ch == '{') {
    //获取到下一个token
    lexer.nextToken();
    
    //上一层是否是数组,fieldName如果为null说明没有上一层
    final boolean parentIsArray = 
        fieldName != null && fieldName.getClass() == Integer.class;

    Map input;
    
    //这个特性是对map的反序列化进行个性定制,默认就是false
    //将input初始化成JSONObject
    if (lexer.isEnabled(Feature.CustomMapDeserializer)) {
        ... ...
    } else {
        input = new JSONObject(lexer.isEnabled(Feature.OrderedField));
    }
    
    //开始设置上下文
    ParseContext ctxLocal = null;

    //将目前的key值设置到上下文里面
    if (!parentIsArray) {
        ctxLocal = setContext(this.context, input, key);
    }

    //开始解析value值
    Object obj = null;
    //value值已经解析完成,初始化为false
    boolean objParsed = false;
    
    ... ...
        
    //递归调用parseObject
    if (!objParsed) {
        obj = this.parseObject(input, key);
    }

    ... ...
}

4.parseObject方法:创建JSONObject对象,读取到 "province": "Guangdong",}

和第二步基本一样,只不过在获取完value之后,有些不同

//第二步会进入到if里面
if (ch == ',') {
    lexer.next();
    continue;
} 
//这一步会进入到这里面
else if (ch == '}') {
    lexer.next();
    lexer.resetStringPosition();
    lexer.nextToken();
    this.setContext(value, key);
    //获取到了{"province": "Guangdong"},返回到上一层
    return object;
}

5.读取到 '}',JSON解析完成。

这一步承接第三部分的源代码

    //接上段
    if (!objParsed) {
        obj = this.parseObject(input, key);
    }
    
   ......
    
    map.put(key, obj);
    
    //parentIsArray是false
    if (parentIsArray) {
        setContext(obj, key);
    }
    
    //会进入到这个条件分支中去
    if (lexer.token() == JSONToken.RBRACE) {
        lexer.nextToken();
    
        setContext(context);
        return object;
    } 

总结

今天主要是了解了什么是递归下降算法,以及fastjson在解析一个json时的简单步骤,更加复杂的情况在之后对源码更加了解之后,可能会详细了解。文章中出现很多fastjson的类,之后会慢慢的了解,比如Feature特征,Token,JSONLexer,SymbolTable等等。希望你能在这篇文章里获得到那么一丁点的知识。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/588860.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于vue3.0简单的页面使用

基于vue3.0简单的页面使用 项目效果图项目文件图package.jsonmain.jsApp.vueviews/Tutorial.vueviews/TS.vueviews/Docs.vueviews/Community.vueviews/Blog.vueviews/About.vueutils/create.jsxutils/defineCom.jsutils/DragIcon.jsutils/someName.tsutils/TS.tsstores/client.…

win11任务栏时间改成12时制

需求&#xff1a;默认24小时值&#xff0c;想改成12小时3:49 方法&#xff1a;设置-时间和语言-语言和区域-管理语言设置-格式 将时间格式改成带tt的

2022年长三角高校数学建模竞赛C题隧道的升级改造与设计解题全过程文档及程序

2022年长三角高校数学建模竞赛 C题 隧道的升级改造与设计 原题再现&#xff1a; 某地现存一旧式双洞隧道&#xff0c;现计划将该隧道在旧貌基础上升级改造。在升级改造前&#xff0c;需进行定标与设计。考虑到该隧道洞壁附着特殊涂料&#xff0c;无人机在洞内通信信号较差&am…

网络面试题:什么是 TCP/IP?

目录标题 什么是 TCP/IP?1) 网络接口层:2) 网络层:3) 传输层:4) 应用层: 2.数据包3.网络接口层4.网络层1) IP:2)地址解析协议 ARP3)子网 5 传输层1&#xff09;UDP&#xff1a;2&#xff09;TCP&#xff1a; 6 应用层运行在TCP协议上的协议&#xff1a;运行在UDP协议上的协议&…

Netty 实现百万级连接服务的难点和优点分析总结

推送服务 还记得一年半前&#xff0c;做的一个项目需要用到 Android 推送服务。和 iOS 不同&#xff0c;Android 生态中没有统一的推送服务。Google 虽然有 Google Cloud Messaging &#xff0c;但是连国外都没统一&#xff0c;更别说国内了&#xff0c;直接被墙。 所以之前在…

Lua学习笔记:C/C++和Lua的相互调用

前言 本篇在讲什么 C/C和Lua的相互调用 本篇适合什么 适合初学Lua的小白 适合需要C/C和lua结合开发的人 本篇需要什么 对Lua语法有简单认知 对C/C语法有简单认知 依赖Lua5.1的环境 依赖VS 2017编辑器 本篇的特色 具有全流程的图文教学 重实践&#xff0c;轻理论&…

云南智慧档案库综合管理系统建设解决方案

一、智慧档案管理系统建设背景 档案作为一种特殊的文献&#xff0c;是人类社会活动的产物&#xff0c;具有特殊的价值&#xff0c;其价值可以概括为现实价值和历史价值。档案是人类留给国家和社会的宝贵财富&#xff0c;它在经济与社会建设中起着重要的作用。档案是反映一个单…

多重共线性的处理方法

回归分析需要考虑多重共线性问题。多重共线性是指自变量之间存在高度相关性&#xff0c;导致回归模型的系数估计不稳定和假设检验不可靠。在实际应用中&#xff0c;许多自变量之间都可能存在一定程度的相关性&#xff0c;如果没有进行控制&#xff0c;就会导致多重共线性问题的…

设计模式之美-实战二:如何对接口鉴权这样一个功能开发做面向对象分析?

面向对象的三个环节&#xff1a;面向对象分析&#xff08;OOA&#xff09;、面向对象设计&#xff08;OOD&#xff09;、面向对象编程&#xff08;OOP&#xff09;。只知道OOA、OOD、OOP只能说有一个宏观了解&#xff0c;我们更重要的还是要知道“如何做”&#xff0c;也就是&a…

【快应用】多语言适配案例

【关键词】 多语言&#xff0c;$t 【问题背景】 快应用平台的能力会覆盖多个国家地区&#xff0c;平台支持多语言的能力后&#xff0c;可以让一个快应同时支持多个语言版本的切换&#xff0c;开发者无需开发多个不同语言的源码项目&#xff0c;避免给项目维护带来困难。使用系…

子串分值--子串分值和 模拟,找规律

子串分值和 n有十万&#xff0c;需要找规律&#xff0c;O(n^2)不满足要求 分析样例&#xff1a; Ababc 01234 长度是n5 索引下标-对应字符 0A贡献 112 a;ab;---22*1 next a 2&#xff1b; pre a -1 1b贡献 112 b;ba;---42*2 next b 3&#xff1b; pre b -1 2a贡献 1113…

2023年测试前景?测试开发工程师养成记,开发企业级测试平台...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 测试开发&#xff…

IDS 和 IPS 日志监控

什么是IDS/IPS 入侵检测系统 &#xff08;IDS&#xff09; 和入侵防御系统 &#xff08;IPS&#xff09; 是监视组织网络中的流量以检测和防止恶意活动和策略违规的网络组件。 入侵检测系统&#xff08;IDS&#xff09;和入侵防御系统&#xff08;IPS&#xff09;可以说是企业…

C语言学习分享(第八次)------数据的存储

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C语言学习分享⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多C语言知识   &#x1f51d;&#x1f51d; 数据的存储 1. 前言&#x1f6a9;2…

现代化智慧档案室建设图文推介

1、防火。建立档案库房防火制度&#xff0c;档案库房附近严禁存放易燃、易爆物品&#xff0c;库房内严禁吸烟&#xff0c;并备有灭火器&#xff0c;经常进行检查更换。 主要设备为&#xff1a;烟雾探测器和感温探测器和七氟丙烷灭火系统。 2、防潮。库房内备有温湿度计&#x…

GB28181 对接海康平台,解决音视频卡顿问题

GB28181 对接海康平台,解决音视频卡顿问题 一、概述二、问题分析1、设备对比分析2、抓包对比分析3、验证分析结果三、总结四、讨论一、概述 设备使用GB28181协议对接海康平台时,发现音频和视频存在卡顿现象,不是一直卡顿,有时候卡有时候不卡,但是卡顿的时候音视频一起卡顿…

炫技操作--递归实现翻转链表(java)

递归实现链表的逆序 leetcode 206题。 翻转链表递归解法普通方式实现链表翻转链表专题 leetcode 206题。 翻转链表 leetcode链接用于测试 题目&#xff1a;描述 将一个链表翻转&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1] 递归解法 解题思路…

chatgpt赋能python:Python中删除的SEO

Python中删除的SEO Python是一个强大的编程语言&#xff0c;它广泛应用于各种领域&#xff0c;包括SEO。在SEO领域中&#xff0c;Python可以用来处理各种数据&#xff0c;包括删除不必要的数据。本文将介绍如何在Python中删除SEO数据。 什么是SEO数据&#xff1f; SEO是搜索…

代码创造的欢乐世界-通用人工智能让儿童熟练应用编程

想要复杂的参考这一篇&#xff0c;使用云平台即可完成&#xff1a; 美美的圣诞树画出来-CoCube- 把圣诞树换成六一儿童节主题的就可以啦。 这一篇是使用chatgpt类应用&#xff0c;给出关键提示词&#xff0c;代码自动生成哦。 神十六发射成功&#xff0c;科技工作者博士学位…

python接口自动化使用requests库发送http请求

目录 前言一、requests库二、HTTP 请求方法三、发送GET请求四、发送POST请求五、获取响应数据六、高级操作 6.1文件下载6.2文件上传6.3SSL证书验证6.4保持会话6.5requests封装总结 前言 今天笔者想和大家来聊聊python接口自动化如何使用requests库发送http请求&#xff0c;废…