高手教您编写简单的JSON解析器

时间:2022-10-07 18:11:43

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

高手教您编写简单的JSON解析器

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

高手教您编写简单的JSON解析器

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

高手教您编写简单的JSON解析器

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

JSON库的接口

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

例如:

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

{"foo": 1})

词汇分析

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

高手教您编写简单的JSON解析器

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

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

高手教您编写简单的JSON解析器

实现一个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语法的一部分(如左圆括号)。如果字符/字符串不匹配任何这些模式,则最后抛出异常。

高手教您编写简单的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解析器

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生成对象,而不是可以在其上执行更多分析(或编译器情况下的代码生成)的语法树。

高手教您编写简单的JSON解析器

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

分析数组

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

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. VIP

    高可用性HA(High Availability)指的是通过尽量缩短因日常维护操作(计划)和突发的系统崩溃(非计划)所导致的停机时间,以提高系统和应用的可用性.HA系统是目前企业防止核心计算机系统因故 ...

  2. 萝卜白菜,给有所爱——C#和JAVA都会终将被时代淘汰

    看到园子里又有一波试图掀起C#和JAVA的谁更好的争论,对于这些一直不断的争论,我觉得实在没有必要,黑格尔的存在即合理,中国的老古语说的萝卜白菜各有所爱,大家争论的再多其实卵用也没用,还不如趁着闲暇时 ...

  3. Winform数据导出Execl小工具

    前台界面.cs文件 using System; using System.Collections.Generic; using System.ComponentModel; using System. ...

  4. BlueDream.js(蓝梦)——jQuery网站使用引导插件

    小菜在前端世界游荡有些时间了,常见的插件多少有些了解,但却很少看到用户引导插件. 所谓用户引导插件,就是在第一次使用某个网站时,会弹出一些小动画,告诉你网站的基本使用方法,帮你快速入门. 这应该是个常 ...

  5. Poj 3030 Nasty Hacks

    1.Link: http://poj.org/problem?id=3030 2.Content: Nasty Hacks Time Limit: 1000MS   Memory Limit: 655 ...

  6. 删除一个目录及其子目录下的所有.svn文件

    今天建立svn,加入代码,发现这些个文件夹中竟然已经有.svn文件夹,也就是它以前使用过svn, 这下就有点麻烦,在全新的svn里,这些.svn需要删除,又不可能一个一个手工去删除 网上翻了一下,发现 ...

  7. ABP之多租户

    “软件多租户”指的是一种软件架构,一个软件实例在一个服务器上运行,但为多个租户服务.租户们对软件实例有通用的访问入口,但是每个租户都有特定的权限. 在多租户体系架构中,用程序旨在为每个租户提供一个专用 ...

  8. JS回调函数中的this指向(详细)

    首先先说下正常的this指向问题 什么是this:自动引用正在调用当前方法的.前的对象. this指向的三种情况 1. obj.fun()     fun中的this->obj,自动指向.前的对 ...

  9. 三、tcp、ip协议详细

    1. 什么是 TCP/IP? TCP/IP 是一类协议系统,它是用于网络通信的一套协议集合. 传统上来说 TCP/IP 被认为是一个四层协议 1) 网络接口层: 主要是指物理层次的一些接口,比如电缆等 ...

  10. 《大话设计模式》C#/C++版pdf/源码下载

    大话设计模式(带目录完整版)[中文PDF+源代码].zip 下载地址:http://pan.baidu.com/s/1giQP4大话设计模式C++.pdf下载地址:http://pan.baidu.c ...