编写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. PHPStorm + Xdebug 调试PHP代码 有大用

    星期四, 12/26/2013 - 19:54 - shipingzhong PHPStorm + Xdebug 调试PHP代码 http://e.v-get.com/2013-11-20 16:55 ...

  2. scau 1079 三角形(暴力)

    </pre>1079 三角形</h1></center><p align="center" style="margin-top: ...

  3. 16/8/23_CSS自动换行

    转载:http://blog.csdn.net/ye987987/article/details/8011875   自动换行问题,正常字符的换行是比较合理的,而连续的数字和英文字符常常将容器撑大,挺 ...

  4. 软件体系结构-分层、代理、MVC、管道与过滤器

    什么是软件架构? 程序或计算系统的软件体系结构是系统的一个或多个结构,包括软件元素.这些元素的外部可见属性以及它们之间的关系. ——Software Engineering Institute(SEI ...

  5. Notepad++正则表达式合并多行代码为1行

    有时候你想要缩减代码行数,多行并1行,像网页流行的JS代码,查看时多见这种情况,但是有时你想把多行switch case缩成1行,再手动分开,每个case一行. 这种对齐方式似乎在日式程序员的代码中经 ...

  6. HTML DOM cursor 属性

    值 描述 url 需被使用的自定义光标的URL 注释:请在此列表的末端始终定义一种普通的光标,以防没有由 URL 定义的可用光标. default 默认光标(通常是一个箭头) auto 默认.浏览器设 ...

  7. css负边距布局

    三行三列的布局 代码结构 <div class="container"> <ul> <li>1</li> <li>2&l ...

  8. GD Library extension not available

    在后台文章上传封面时,遇到了这样一个错误 GD Library extension not available with this PHP installation Ubuntu Nginx 自己在本 ...

  9. 洛谷P5018 对称二叉树——hash

    给一手链接 https://www.luogu.com.cn/problem/P5018 这道题其实就是用hash水过去的,我们维护两个hash 一个是先左子树后右子树的h1 一个是先右子树后左子树的 ...

  10. HashMap和布隆过滤器命中性能测试

    package datafilter; import com.google.common.base.Stopwatch; import com.google.common.hash.BloomFilt ...