Python 实现 JSON 解析器

news/2023/5/28 8:09:00

Json 解析

文章目录

  • Json 解析
    • Json 的组成
      • 对象结构
      • 数组结构
    • 词法分析
    • 逻辑性解析
      • 解析对象类型
      • 解析数组类型
    • 完整代码
    • 小结

Json 的组成

JSON结构共有2种

  1. 对象结构
  2. 数组结构

一个合法的JSON字符串可以包含这几种元素:

  1. 特殊符号,如"{" “}“表示一个JSON Object,”[” "]“表示一个JSON Array,”:“用于分隔key-value,”,"用于分隔两个元素
  2. 字符串,用引号引起来
  3. 数字,包含0-9,浮点数带有".“,表示符号可带有”+" “-”
  4. 常量有true, false, null

对象结构

对象结构是使用大括号“{”括起来的,大括号是由0个或多个用英文逗号分隔的“关键字:值”对(key:value)构成。

语法:

{"健名1""值1","健名2""值2"
}

说明:

jsonObj指的是json对象。对象结构是以"{“开始,到”}"结束。其中"键值"和"值"之间英文冒号构成对,两个"键名:值"之间用英文逗号分隔。

注意,这里的键名是字符串,但是值可以是数值、字符串、对象、数组或逻辑true和false。

数组结构

JSON数组结构是用中括号"[]“括起来,中括号内部由0个或多个英文逗号”,"分隔的值列表组成。

语法:

["name": "ywh","age": 18
]

说明:

数组结构是以"{“开始,到”]“结束,这一点跟JSON对象不同。在JSON数组中,每一对”{}"相当于一个JSON对象,大家看看像不像?而且语法都非常类似。

注意,这里的键名是字符串,但是值可以是数值、字符串、对象、数组或逻辑true和false。

词法分析

词法分析的主要目的是将字符串解析成一小组一小组的合法元素,要将那些是结构所需的符号,数据表达的符号识别出来。比如要将字符串+12.0解析为一个数字12.0等等。

可以分析json的组成发现,特殊的元素都只包含一个字符,常量,数字的表示中间则不会出现空格或其他不相关字符,因此可以轻易地用表达的特征区分,如:

  • 数字 可以匹配一段连续的且所有字符都在0-9或者是("." "+" "-")的范围内。

  • 字符串 对于字符串我们则只需要考虑在双引号"之间的任何字符。特殊的话我们需要考虑到字符串中的特殊的转义字符比如字符\"转义之后的意思其实就是"

  • 常量 对于(true, false, null)这些常量与字符串的不同之处就是它们不会被"所包裹。所以我们只需要读取结构分隔符(例如:(":", ",", "]","}"))之间的字符串,并在匹配字符后查看是否有对应的常量。

  • 空格字符 空格字符一般,让我们的json数据看起来结构更加清晰。但是在解析到相应语言的数据结构的时候则不需要考虑,所以遇到有效字符片段后的空格都跳过。

  • 结构标志性字符 一般单独出现,并且位置比较特殊。比如{作为对象的开始,在对象中:的下一个有效字符片段作为相应value

后面将这些特殊的元素组合称为 合法字符组

所以json解析器要做的就是,如何识别出字符串中一小组一小组的合法元素,同时要根据 结构标志性字符 将得到的合法元素组逻辑拼接起来并检查其中是否有非法格式。

因此编写了下面这个Tokener类:

class Tokener():"""## TODO: 字符串中的各种 Token 解析"""def __init__(self, json_str):self.__str = json_str    # json 字符串self.__i = 0   #  当前读到的字符位置self.__cur_token = None    # 当前的字符def __cur_char(self):"""## 读取当前的字符的位置"""if self.__i < len(self.__str):return self.__str[self.__i]return ''def __move_i(self, step=1):"""## 读取下一个字符"""if self.__i < len(self.__str): self.__i += stepdef __next_string(self):"""## str 字符片段读取"""outstr = ''trans_flag = Falseself.__move_i()while  self.__cur_char() != '':ch = self.__cur_char()if ch == "\\": trans_flag = True  # 处理转义else:if not trans_flag:if ch == '"':breakelse:trans_flag = Falseoutstr += chself.__move_i()return outstrdef __next_number(self):"""## number 字符片段读取"""expr = ''while  self.__cur_char().isdigit() or self.__cur_char() in ('.', '+', '-'):expr += self.__cur_char()self.__move_i()self.__move_i(-1)if "." in expr: return float(expr)else: return int(expr)def __next_const(self):"""## bool 字符片段读取"""outstr = ''while self.__cur_char().isalpha() and len(outstr) < 5:outstr += self.__cur_char()self.__move_i()self.__move_i(-1)if outstr in ("true", "false", "null"):return {"true": True,"false": False,"null": None,}[outstr]raise Exception(f"Invalid symbol {outstr}")def next(self):"""## 解析这段 json 字符串## TODO: 获得下一个字符片段(合法字符组)"""is_white_space = lambda a_char: a_char in ("\x20", "\n", "\r", "\t")while is_white_space(self.__cur_char()):self.__move_i()ch = self.__cur_char()if ch == '':cur_token = Noneelif ch in ('{', '}', '[', ']', ',', ':'):# 这些特殊的包裹性、分隔性字符作为单独的tokencur_token = chelif ch == '"':# 以 “ 开头代表是一个字符串cur_token = self.__next_string()elif ch.isalpha():# 直接以字母开头的话检查是不是 bool 类型的cur_token = self.__next_const()elif ch.isdigit() or ch in ( '-', '+'):# 以数字开头或者是+-符号开头cur_token = self.__next_number()self.__move_i()self.__cur_token = cur_tokenreturn cur_token is not Nonedef cur_token(self):# 当前的合法元素组return self.__cur_token

代码注释中的 Token 就可以理解为合法字符组。

调用这个类中的next函数就可以获得下一个合法的字符组。也是将其中的字符串、数字、常量、特殊性分隔符识别出来。

逻辑性解析

首先我们需要去区分两种json类型

image-20230118224614303

上图做的就是确定,这个json最外层属于哪种json类型的,然后根据两种类型的特征去解析。

解析对象类型

image-20230118225511356

解析数组类型

image-20230118230042946

完整代码

class Tokener():"""## TODO: 字符串中的各种 Token 解析"""def __init__(self, json_str):self.__str = json_str    # json 字符串self.__i = 0   #  当前读到的字符位置self.__cur_token = None    # 当前的字符def __cur_char(self):"""## 读取当前的字符的位置"""if self.__i < len(self.__str):return self.__str[self.__i]return ''def __move_i(self, step=1):"""## 读取下一个字符"""if self.__i < len(self.__str): self.__i += stepdef __next_string(self):"""## str 字符片段读取"""outstr = ''trans_flag = Falseself.__move_i()while  self.__cur_char() != '':ch = self.__cur_char()if ch == "\\": trans_flag = True  # 处理转义else:if not trans_flag:if ch == '"':breakelse:trans_flag = Falseoutstr += chself.__move_i()return outstrdef __next_number(self):"""## number 字符片段读取"""expr = ''while  self.__cur_char().isdigit() or self.__cur_char() in ('.', '+', '-'):expr += self.__cur_char()self.__move_i()self.__move_i(-1)if "." in expr: return float(expr)else: return int(expr)def __next_const(self):"""## bool 字符片段读取"""outstr = ''while self.__cur_char().isalpha() and len(outstr) < 5:outstr += self.__cur_char()self.__move_i()self.__move_i(-1)if outstr in ("true", "false", "null"):return {"true": True,"false": False,"null": None,}[outstr]raise Exception(f"Invalid symbol {outstr}")def next(self):"""## 解析这段 json 字符串## TODO: 获得下一个字符片段"""is_white_space = lambda a_char: a_char in ("\x20", "\n", "\r", "\t")while is_white_space(self.__cur_char()):self.__move_i()ch = self.__cur_char()if ch == '':cur_token = Noneelif ch in ('{', '}', '[', ']', ',', ':'):# 这些特殊的包裹性、分隔性字符作为单独的tokencur_token = chelif ch == '"':# 以 “ 开头代表是一个字符串cur_token = self.__next_string()elif ch.isalpha():# 直接以字母开头的话检查是不是 bool 类型的cur_token = self.__next_const()elif ch.isdigit() or ch in ( '-', '+'):# 以数字开头或者是+-符号开头cur_token = self.__next_number()self.__move_i()self.__cur_token = cur_tokenreturn cur_token is not Nonedef cur_token(self):return self.__cur_tokenclass JsonDecoder():"""TODO: json 字符串解析"""def __init__(self):passdef __json_object(self, tokener):"""## json 解析json中的对象结构"""obj = {}# 判断是否为 object 起始字符 if tokener.cur_token() != "{":raise Exception('Json must start with "{"')while True:  # 循环中每次都去解析一组键值对tokener.next()tk_temp = tokener.cur_token()# 如果直接遇到闭回的 } 则直接返回空结构体if tk_temp == "}":return {}if not isinstance(tk_temp, str):raise Exception(f'invalid key {tk_temp}')# 解析得到 键值对中  keykey = tk_temptokener.next()if tokener.cur_token() != ":":raise Exception(f'expect ":" after {key} ')# 解析得到 键值对中 valuetokener.next()val = tokener.cur_token()if val == "[":val = self.__json_array(tokener)elif val == "{":val = self.__json_object(tokener)obj[key] = val# 解析与下一组键值对的tokener.next()tk_split = tokener.cur_token()if tk_split == ",":    # 若遇到多个元素则重新进入循环continueelif tk_split == "}":    # 如果为 } 则说明对象闭合breakelse:if tk_split is None:print(f"tk_split {tk_split}")raise Exception('missing "}" at the end of object')raise Exception(f'unexpected token "{tk_split}" at key "{tk_split}" ')return objdef __json_array(self, tokener):if tokener.cur_token() != "[":raise Exception('Json array must start with "["')arr = []while True:# 每次遍历都能得到数组中的一个元素tokener.next()tk_temp = tokener.cur_token()if "tk_temp" == "]": return arrif tk_temp == "{":val = self.__json_object(tokener)elif tk_temp == "[":val = self.__json_array(tokener)elif tk_temp in (',', ":", "}"):raise  Exception(f"unexpected token {tk_temp}")else:val = tk_temparr.append(val)# 解析获得与数值的逻辑连接符tokener.next()tk_end = tokener.cur_token()if tk_end == ",":continueif tk_end == "]":breakelse:if tk_end is None:raise  Exception('missing "]" at the end of array')return arrdef decode(self, json_str:str):tokener = Tokener(json_str)if not tokener.next():return Nonefirst_token = tokener.cur_token()if first_token == "{":decode_val = self.__json_object(tokener)elif first_token == "[":decode_val = self.__json_array(tokener)else:raise Exception('Json must start with "{"')if tokener.next():raise Exception(f"unexpected token {tokener.cur_token()}")return decode_valdef get_testData():"""## 获得测试数据"""with open("./data.json", "r", encoding="utf-8") as file:return file.read()def test():data = get_testData()decoder = JsonDecoder()print(decoder.decode(data))if __name__ == '__main__':test()

小结

总结一下解析中的要点:

  • 分析元始格式化数据的格式特点

  • 解析字符串中的合法字符组

  • 通过逻辑符将解析得到的合法字符组逻辑串联起来

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

如若内容造成侵权/违法违规/事实不符,请联系郑州代理记账网进行投诉反馈,一经查实,立即删除!

相关文章

基于DDD模型的分层架构图分享

分享一个今天画的基于DDD模型的微服务分层架构图 有问题&#xff0c;可以留言交流。 本来在应用层也定义了一个“资源库接口”&#xff0c;后来想想为了避免开发人员分不清应用层的资源库接口&#xff08;一般为只读&#xff09;和领域层的资源库接口&#xff0c;所以不能太…

我们团队是如何落地DDD的(1)

最近发现文章老是被窃取&#xff0c;有些平台举报了还没有用。请识别我的id方丈的寺院。 摘要 DDD领域驱动设计&#xff0c;起源于2004年著名建模专家Eric Evans发表的他最具影响力的著名书籍&#xff1a;Domain-Driven Design –Tackling Complexity in the Heart of Softwa…

DDD之4聚合和聚合根

聚合就是归类的意思&#xff0c;把同类事物统一处理&#xff1b; 聚合根也就是最抽象&#xff0c;最普遍的特性&#xff1b; 背景 领域建模的过程回顾&#xff1a; 那么问题来了&#xff1f; 为什么要在限界上下文和实体之间增加聚合和聚合根的概念&#xff0c;即作用是什么…

DDD-领域对象与领域服务

问题 什么是领域对象什么是领域服务领域对象的行为&#xff0c;与领域服务的行为区别 原因 为什么把这么小的点拿出来讲&#xff0c;最开始在讨论中领域对象与领域服务时&#xff0c;觉得行为放在service/entity中区别不大&#xff0c;只是一个放置位置的问题&#xff0c;并…

DDD-CQRS能解什么问题

背景 在DDD代码实践过程出现一些看起来很别扭的实现 为了查询&#xff0c;领域聚合根无限扩大 如商品详情页聚合根 public class BrandAggr {/*** 唯一标识*/private Long id;/*** 商品简介*/private ItemInfoVal brandInfoVal;/*** 商品的渠道列表*/private List<ItemCh…

DDD领域驱动设计-DDD开源框架xtoon-boot

基于DDD领域模型并支持SaaS平台的企业级开发脚手架. 开源地址&#xff1a;https://gitee.com/xtoon/xtoon-boot 官网&#xff1a;http://xtoon-boot.xiangtoon.com 在线演示&#xff1a;http://xtoon-boot.demo.xiangtoon.com 如果有什么问题或建议可以加群&#xff08;QQ&…

可落地的DDD(3)-如何利用DDD进行微服务的划分

摘要 前面两篇介绍了DDD的目标管理、DDD的工程结构调整。这篇讨论微服务的划分。微服务是目前后端比较流行的架构体系了&#xff0c;那么如何做好一个微服务的划分&#xff1f;一个微服务的粒度应该是多大呢&#xff1f;这篇主要介绍如何结合DDD进行领域划分。 工程结构代码 …

DDD建模方法论之【事件风暴法】

DDD建模方法论之【事件风暴法】 前言 在领域驱动设计之初的需求分析阶段&#xff0c;对需求分析的基本思路就是统一语言建模&#xff0c;它是我们的指导思想。落实到具体操作层面&#xff0c;可以采用的具体方法是事件风暴法。 统一语言建模 -> 指导思想。 事件风暴会议 -…

领域驱动设计(DDD)之分层架构

前言 由于由近几年微服务架构兴起&#xff0c;领域驱动设计&#xff08;DDD&#xff09;也被大多领域专家重新看待。但是其实这两者本来是不相关的两个东西&#xff0c;2004年著名建模专家Eric Evans发表了他最影响力的书籍《领域驱动设计》&#xff0c;提了现在如日中天的架构…

DDD-CQRS的落地案例

摘要 在之前的文章DDD-CQRS能解什么问题中&#xff0c;阐述了什么是CQRS。但是并没有业务需求可以应用CQRS。最近需要处理一个文本增量更新的业务&#xff0c;经过需求分析后&#xff0c;尝试使用CQRS来解这个问题 问题分析 一个文本页面编辑&#xff0c;对象很大&#xff0…

DDD之代码架构

点击↑上方↑蓝色“编了个程”关注我~每周至少一篇原创文章这是本公众号的第 33 篇原创文章荒腔走板这是一篇迟到的文章。这其实是我写DDD的第四篇文章。去年11月份左右我在个人网站上写了三篇关于DDD的文章&#xff0c;都是比较偏战略部分的。那个时候我还在一个正在使用DDD的…

图的关键路径(AOE网络)

文章目录AOE网概念性质研究的问题关键路径概念求解的方法注意事项AOE网 概念 用顶点表示事件, 边弧表示活动, 边弧上的权值表示活动持续的时间, 这样的带权有向无环图叫AOE网. AOE网常用于估算工程完成时间. AOE网和AOV网都是有向无环图, 不同之处在于它们的边和顶点所代表的…

什么是永续合约?

什么是永续合约&#xff1f; #Be_a_Trader! MCS永续合约合约商品是以BTC/USDT作为交易对的反向永续合约。 永续合约是被称为”数字货币期货“的金融衍生商品。永续合约兼有现货交易和期货交易的特点&#xff0c;但和现货、期货交易也有一定区别。 无到期日、交割日 与需要在…

EOS.IO 将迎来史上最复杂硬分叉升级,硬分叉的流行意味着什么?

1.8.0是EOS.IO开源以来最重要的版本&#xff0c;将奠定EOS.IO今后通过硬分叉升级快速迭代的基调&#xff0c;也会让整个区块链行业重新审视硬分叉升级的重要性。 &#xff08;孤矢&#xff0c;EOS 原力创始人&#xff09; 2019 年年初&#xff0c;一些媒体开始报道 EOS.IO 代码…

4.Java的基础语法

小伙伴们,本篇内容让我们一起来总结学习Java的基础语法吧!&#x1f609; 文章目录一、注释二、关键字三、字面量(也被叫做:常量/字面值常量)四、一些特殊字面量的书写五、变量(1)变量的定义格式:(2)输出打印变量:(3)变量的基本用法:(4)变量的注意事项:(5)变量的练习总结一、注释…

360oauth token是什么意思_Coin还是Token?背后的逻辑是什么?

下文内容同视频~https://www.zhihu.com/video/1167101256702353408我们上次的节目说过比特币其实并不是货币&#xff0c;而是资产&#xff0c;虽然广义上依然是“钱”的概念&#xff0c;但大家有没有注意到&#xff0c;其实无论是币圈还是链圈&#xff0c;我们都极少听见有人把…

什么是生命周期终止(EOL)?

End Of Life(EOL) is used in computing especially in hardware and software to define the end of the production. Especially commercial manufacturers use EOL in order to finalize the production with old hardware and software and after some time stopping the

EOS 数据库与持久化 API —— 架构

在 EOS 中&#xff0c;智能合约执行完毕后&#xff0c;所占用的内存会释放。程序中的所有变量都会丢失。如果智能合约里要持久地记录信息&#xff0c;比如游戏智能合约要记录每位用户游戏记录&#xff0c;本次合约执行完毕后数据不能丢失&#xff0c;就需要将数据存储到 EOS 数…

机器学习笔记之高斯分布(一)——使用极大似然估计计算最优参数

机器学习笔记之高斯分布——使用极大似然估计计算最优参数目录高斯分布介绍一维正态分布多维正态分布回顾&#xff1a;数据集合与概率模型使用极大似然估计计算高斯分布最优参数目录 本节将介绍一个在统计机器学习中占据重要地位的分布——高斯分布。 高斯分布介绍 高斯分布…

多元线性回归,参数估计,模拟,最小二乘法,极大似然估计,matlab

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、多元线性回归二、最小二乘法&#xff0c;极大似然估计三、使用步骤1.产生数据&#xff08;和上一篇文章代码一样&#xff09;2.方法3.完整代码总结前言 多元…