编写JSON解析器是熟悉解析技术的最简单方法之一。格式非常简单。它是递归定义的,所以与解析Brainfuck相比,你会遇到轻微的挑战 ; 你可能已经使用JSON。除了最后一点之外,解析 Scheme的S表达式可能是更简单的任务。

解析通常分为两个阶段:词法分析和句法分析。词法分析将源输入分解为称为“令牌”的语言中最简单的可分解元素。句法分析(通常称为“解析”)会接收到令牌列表,并尝试查找其中的模式以符合要解析的语言。

解析不能确定输入源的语义可行性。输入源的语义可行性可能包括变量是否在使用之前定义,是否使用正确的参数调用函数,或者是否可以在某个范围内第二次声明变量。

当然,人们选择解析和应用语义规则的方式总是有所不同,但我正在假设一种“传统”方法来解释核心概念。

JSON库的接口

最终,应该有一个from_string方法接受一个JSON编码的字符串并返回等效的Python字典。

例如:

assert_equal(from_string('{"foo": 1}'),

{"foo": 1})

词汇分析

词法分析将输入字符串分解为令牌。注释和空白在词法分析过程中经常被丢弃,因此您只需输入一个简单的输入即可在语法分析过程中搜索语法匹配。

假设一个简单的词法分析器,您可以代替输入字符串中的所有字符,并将它们拆分为非整数,字符串和布尔文字等非递归定义的语言结构。特别是,字符串 必须是词法分析的一部分,因为在不知道它不是字符串的一部分的情况下,不能丢弃空格。

在一个有用的词法分析器中,您可以跟踪您跳过的空格和注释,当前行号和文件,以便您可以在任何阶段通过分析源代码所产生的错误中引用它。V8 Javascript引擎最近能够重现一个函数的确切源代码。这至少需要词法分析器的帮助才能成为可能。

实现一个JSON词法分析器

JSON词法分析器的要点是遍历输入源,并尝试查找字符串,数字,布尔值,空值或JSON语法(如左圆括号和左括号)的模式,最终将每个元素作为列表返回。

以下是词法分析器应该为示例输入返回的内容:

assert_equal(lex('{"foo": [1, 2, {"bar": 2}]}'),

['{', 'foo', ':', '[', 1, ',', 2, '{', 'bar', ':', 2, '}', ']', '}'])

以下是这种逻辑可能开始看起来像:

def lex(string):

tokens = []

while len(string):

json_string, string = lex_string(string)

if json_string is not None:

tokens.append(json_string)

continue

# TODO: lex booleans, nulls, numbers

if string[0] in JSON_WHITESPACE:

string = string[1:]

elif string[0] in JSON_SYNTAX:

tokens.append(string[0])

string = string[1:]

else:

raise Exception('Unexpected character: {}'.format(string[0]))

return tokens

这里的目标是尝试匹配字符串,数字,布尔值和空值并将它们添加到令牌列表中。如果这些都不匹配,检查字符是否为空白,如果是,则将其丢弃。否则,将其作为标记存储,如果它是JSON语法的一部分(如左圆括号)。如果字符/字符串不匹配任何这些模式,则最后抛出异常。

让我们在这里扩展核心逻辑以支持所有类型并添加函数存根。

def lex_string(string):

return None, string

def lex_number(string):

return None, string

def lex_bool(string):

return None, string

def lex_null(string):

return None, string

def lex(string):

tokens = []

while len(string):

json_string, string = lex_string(string)

if json_string is not None:

tokens.append(json_string)

continue

json_number, string = lex_number(string)

if json_number is not None:

tokens.append(json_number)

continue

json_bool, string = lex_bool(string)

if json_bool is not None:

tokens.append(json_bool)

continue

json_null, string = lex_null(string)

if json_null is not None:

tokens.append(json_null)

continue

if string[0] in JSON_WHITESPACE:

string = string[1:]

elif string[0] in JSON_SYNTAX:

tokens.append(string[0])

string = string[1:]

else:

raise Exception('Unexpected character: {}'.format(string[0]))

return tokens

Lexing字符串

对于该lex_string功能,要点是检查第一个字符是否是报价。如果是,则遍历输入字符串,直到找到结尾引号。如果您没有找到初始报价,请返回无和原始列表。如果您发现初始报价和结束报价,请返回报价中的字符串以及未经检查的输入字符串的其余部分。

def lex_string(string):

json_string = ''

if string[0] == JSON_QUOTE:

string = string[1:]

else:

return None, string

for c in string:

if c == JSON_QUOTE:

return json_string, string[len(json_string)+1:]

else:

json_string += c

raise Exception('Expected end-of-string quote')

对于这个lex_number函数,要点是遍历输入,直到找到一个不能成为数字的字符。(当然,这是一种粗略的简化,但更准确的将作为练习留给读者。)在找到不能作为数字一部分的字符后,如果您使用的字符不是返回浮点数或int累计数大于0.否则返回None和原始字符串输入。

def lex_number(string):

json_number = ''

number_characters = [str(d) for d in range(0, 10)] + ['-', 'e', '.']

for c in string:

if c in number_characters:

json_number += c

else:

break

rest = string[len(json_number):]

if not len(json_number):

return None, string

if '.' in json_number:

return float(json_number), rest

return int(json_number), rest

Lexing布尔和空值

查找布尔值和空值是一个非常简单的字符串匹配。

def lex_bool(string):

string_len = len(string)

if string_len >= TRUE_LEN and \

string[:TRUE_LEN] == 'true':

return True, string[TRUE_LEN:]

elif string_len >= FALSE_LEN and \

string[:FALSE_LEN] == 'false':

return False, string[FALSE_LEN:]

return None, string

def lex_null(string):

string_len = len(string)

if string_len >= NULL_LEN and \

string[:NULL_LEN] == 'null':

return True, string[NULL_LEN]

return None, string

现在,词法分析器代码已经完成!

句法分析

语法分析器(基本)的工作是遍历一维的令牌列表,并根据语言的定义将令牌组匹配到多个语言片断。如果在句法分析过程中的任何时候,解析器都无法将当前的一组令牌与语言的有效语法进行匹配,那么解析器将会失败,并可能为您提供有用的信息,包括您给出的内容,位置以及期望的内容您。

实现JSON解析器

JSON解析器的要点是对调用后接收到的令牌进行迭代,lex并尝试将令牌与对象,列表或普通值进行匹配。

以下是解析器应该为示例输入返回的内容:

tokens = lex('{"foo": [1, 2, {"bar": 2}]}')

assert_equal(tokens,

['{', 'foo', ':', '[', 1, ',', 2, '{', 'bar', ':', 2, '}', ']', '}'])

assert_equal(parse(tokens),

{'foo': [1, 2, {'bar': 2}]})

以下是这种逻辑可能开始看起来像:

def parse_array(tokens):

return [], tokens

def parse_object(tokens):

return {}, tokens

def parse(tokens):

t = tokens[0]

if t == JSON_LEFTPAREN:

return parse_array(tokens[1:])

elif t == JSON_LEFTBRACKET:

return parse_object(tokens[1:])

else:

return t, tokens[1:]

这个词法分析器和解析器之间的一个关键结构区别是词法分析器返回一个一维的标记数组。解析器通常是递归定义的,并返回一个递归的树状对象。由于JSON是数据序列化格式而不是语言,因此解析器应该使用Python生成对象,而不是可以在其上执行更多分析(或编译器情况下的代码生成)的语法树。

而且,词法分析独立于解析器的好处在于,这两个代码都比较简单,只关注特定的元素。

分析数组

解析数组需要解析数组成员,并期望它们之间有一个逗号标记或指示数组结尾的右圆括号。

def parse_array(tokens):

json_array = []

t = tokens[0]

if t == JSON_RIGHTPAREN:

return json_array, tokens[1:]

while True:

json, tokens = parse(tokens)

json_array.append(json)

t = tokens[0]

if t == JSON_RIGHTPAREN:

return json_array, tokens[1:]

elif t != JSON_COMMA:

raise Exception('Expected comma after object in array')

else:

tokens = tokens[1:]

raise Exception('Expected end-of-array round bracket')

解析对象

解析对象是一个解析由冒号内部分隔的键值对,外部用逗号分隔的对象,直到到达对象的末尾。

def parse_object(tokens):

json_object = {}

t = tokens[0]

if t == JSON_RIGHTBRACKET:

return json_object, tokens[1:]

while True:

json_key = tokens[0]

if type(json_key) is str:

tokens = tokens[1:]

else:

raise Exception('Expected string key, got: {}'.format(json_key))

if tokens[0] != JSON_COLON:

raise Exception('Expected colon after key in object, got: {}'.format(t))

json_value, tokens = parse(tokens[1:])

json_object[json_key] = json_value

t = tokens[0]

if t == JSON_RIGHTBRACKET:

return json_object, tokens[1:]

elif t != JSON_COMMA:

raise Exception('Expected comma after pair in object, got: {}'.format(t))

tokens = tokens[1:]

raise Exception('Expected end-of-object bracket')

现在解析器代码已经完成!查看 代码的 pj / parser.py作为一个整体。为了提供理想的界面,创建from_string包装lex和parse功能的函数。

def from_string(string):

tokens = lex(string)

return parse(tokens)[0]

编写完毕!文章来源:黑客周刊

 

高手教您编写简单的JSON解析器的更多相关文章

  1. 自己动手实现一个简单的JSON解析器

    1. 背景 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着诸多优点.比如易读性更好,占用空间更少等.在 ...

  2. 一个简单的json解析器

    实现一个简单地json解析器. 两部分组成,词法分析.语法分析 词法分析 package com.mahuan.json; import java.util.LinkedList; import ja ...

  3. 用c#自己实现一个简单的JSON解析器

    一.JSON格式介绍 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着很多优点.例如易读性更好,占用空间更 ...

  4. 如何编写一个JSON解析器

    编写一个JSON解析器实际上就是一个函数,它的输入是一个表示JSON的字符串,输出是结构化的对应到语言本身的数据结构. 和XML相比,JSON本身结构非常简单,并且仅有几种数据类型,以Java为例,对 ...

  5. 一起写一个JSON解析器

    [本篇博文会介绍JSON解析的原理与实现,并一步一步写出来一个简单但实用的JSON解析器,项目地址:SimpleJSON.希望通过这篇博文,能让我们以后与JSON打交道时更加得心应手.由于个人水平有限 ...

  6. 用ExpressionTree实现JSON解析器

    今年的春节与往年不同,对每个人来说都是刻骨铭心的.突入其来的新型冠状病毒使大家过上了“梦想”中的生活:吃了睡,睡了吃,还不用去公司上班,如今这样的生活就在我们面前,可一点都不踏实,只有不停的学习才能让 ...

  7. 面试题|手写JSON解析器

    这周的 Cassidoo 的每周简讯有这么一个面试题:: 写一个函数,这个函数接收一个正确的 JSON 字符串并将其转化为一个对象(或字典,映射等,这取决于你选择的语言).示例输入: fakePars ...

  8. 手写Json解析器学习心得

    一. 介绍 一周前,老同学阿立给我转了一篇知乎回答,答主说检验一门语言是否掌握的标准是实现一个Json解析器,网易游戏过去的Python入门培训作业之一就是五天时间实现一个Json解析器. 知乎回答- ...

  9. SLAM+语音机器人DIY系列:(二)ROS入门——5.编写简单的消息发布器和订阅器

    摘要 ROS机器人操作系统在机器人应用领域很流行,依托代码开源和模块间协作等特性,给机器人开发者带来了很大的方便.我们的机器人“miiboo”中的大部分程序也采用ROS进行开发,所以本文就重点对ROS ...

随机推荐

  1. mybatis获取数据库自增id

    http://blog.csdn.net/dyllove98/article/details/8866357 http://www.iteye.com/problems/86864 insert标签中 ...

  2. Centos7开机自动启动服务和联网

    虚拟机设置选择NAT模式,默认情况下,Centos不是自动连接上网的,需要点击右上角,手动连接上网. 可以修改开机启动配置修改: 1. cd 到/etc/sysconfig/network-scrip ...

  3. 10个最容易犯的Python开发错误

    10个最容易犯的Python开发错误 转载 2017年09月25日 16:54:36 标签: python / 大数据 / 大讲台   Python是一门简单易学的编程语言,语法简洁而清晰,并且拥有丰 ...

  4. 洛谷P1441 砝码称重(搜索,dfs+bitset优化)

    洛谷P1441 砝码称重 \(n\) 的范围为 \(n \le 20\) ,\(m\) 的范围为 \(m \le 4\) . 暴力遍历每一种砝码去除情况,共有 \(n^m\) 种情况. 对于剩余砝码求 ...

  5. Android中国官网资源网站

    现在android开发者官网在中国有中文版已经不是太大的新闻,为了平时查询资料和学习方便,记录如下. PS:建议用Google浏览器浏览,你懂的!! https://developers.google ...

  6. Linux 两台服务器之间传递文件

    参考: https://www.cnblogs.com/clovershell/p/9870603.html linux采用scp命令拷贝文件到本地,拷贝本地文件到远程服务器   // 假设远程服务器 ...

  7. pycharm5.0.4简易使用说明

    前言:学习自动化,需要使用pycharm,以下是简易使用说明 1.注册破解 2.行号和背景色 3.打断点 1.注册破解 打开pycharm5.0.4,点击菜单栏的help->register.. ...

  8. IoC与DI,Unity的使用

    IoC的全称为Inversion of Control(控制反转),DI的全称为Dependency Injection(依赖注入).IoC是一个控制容器,我们将设计好的对象放入到容器中,将对象交给容 ...

  9. 下载工具 qBittorrent 使用

    官网地址,软件可以在官网上下载. GitHub 源码 知乎的参考链接 qBittorrent 是开源软件,支持用 BT 种子或种子的链接下载,也可以用磁力链接进行下载. 搜索功能 qBittorren ...

  10. Java基础复习(1)

    1. Java 基本数据类型 参考博客: https://www.cnblogs.com/LiaHon/p/11043238.html Java语言提供了八种基本类型. 六种数字类型(四个整数型,两个 ...