Writing a simple JSON parser 写一个简单的JSON解析器

这篇主要参考@phil_eaton 的 "Writing a simple JSON parser" 写的,用于参考学习

他的这这篇文章链接:Writing a simple JSON parser | notes.eatonphil.com

关键词:json, parsing, python

概要:写一个JSON解析器是一个很棒的办法,可以让你熟悉解析的科技。其实之前我还不知道JSON是什么。接下来我会从以下主题介绍

What JSON is — 什么是 JSON?

JSON 是 一种纯字符串形式的数据

全称为 "JavaScript Object Notation" — “JavaScript对象表示法”,一种轻量级、基于文本的、开放的数据交换格式

数据交换是指:两个设备之间建立连接并相互传递数据的过程

What parsing is and (typically) is not

解析器的工作程序:输入文本(字符串) — 构建数据结构。Parsing is a tool like this.

分为两个部分: Lexical analysis & Syntactic analysis (词法和句法)

The JSON library's interface

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

Lexical analysis

# 应用一个词法分析器
assert_equal(lex('{"foo": [1, 2, {"bar": 2}]}'), 
    ['{', 'foo', ':', '[', 1, ',', 2, ',', '{', 'bar', ':', 2, '}', ']', '}'])

词法分析器是这样工作的:

   首先有一个输入的字符串,其次将这个字符串打碎拆分成不同类型的标志词,然后没了。

   简单来说:字符串拆成词。很简单吧
 

JSON格式有以下格式

   字符串**string**,

   数字**number**,

   布尔值**bool**,

   空**null**

表达式要符合要求,还要有这些

  引号**COLON**,

  逗号**COMMA**,

  左右大括号**{}**, 

  左右中括号**[]**

这些会装在JSON_SYNTAX的元组(tuple)里面

JSON_SYNTAX = [',', ':', '{', '}', '[', ']' ]

是在输入字符串的时候,还会有一些**“空”字符**,这些是什么呢:

        空格 ,tab(制表定位键)\t,换行\r,回车\n,退格\b(我也不知道为什么有这个东西)

        这些会装在JSON_WHITESPACE的元组(tuple)里面

JSON_WHITESPACE = [' ', '\t', '\r', '\n', '\b' ]

有了这些数据,接下来是函数的主体了。作为一个词法分析器,首先最重要的就是能够识别不同类型的词,函数结构大致长这样:

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)      # json_string是解析出的字符串,如果没有则设空;string是余下的输入字符串,
        if json_string is not None:
            tokens.append(json_string)
            continue

        json_number, string = lex_number(string)      # json_number是解析出的数,如果没有则设空;string是余下的输入字符串,
        if json_number is not None:
            tokens.append(json_number)
            continue

        json_bool, string = lex_bool(string)          # json_bool是解析出的布尔值,非空则赋值为字符串类型的'ture'或'false',如果没有则设空;string是余下的输入字符串,
        if json_bool is not None:
            tokens.append(json_bool)
            continue

        json_null, string = lex_null(string)          # json_null是解析出的null,非空则赋值为字符串类型的'null',如果没有则设空;string是余下的输入字符串,
        if json_null is not None:
            tokens.append(None)
            continue

        if string[0] in JSON_WHITESPACE:              # 如果是“空”(空格等)则吞掉
            string = string[1:]
        elif string[0] in JSON_SYNTAX:                # 如果是JSON的符号(逗,冒,括号)则加入元组
            tokens.append(string[0])
            string = string[1:]
        else:
            raise Exception('Unexpected character: {}'.format(string[0]))  # 将无法识别类型的字符打印并报错
                                                                           # 这个顺序就是先解析一个词再解析一个JSON符号
    return tokens

Lexing strings

检查顺序:冒号,读取字符串,冒号

在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
    else:
      json_string +=c
  # 如果读完字符串也没有遇到第二个冒号(字符串结束标志)
  raise Exception('Except end-of-string quote')

TODO:lexing numbers, boolens, nulls

Lexing numbers

检查顺序:数字,直到非数字

一个合法的数字有以下形式,包含的字符有0-9,负号,点,e

1, 2222, 1433223, 2745148060   # 正整数
8.88, 0.01, 120348.21384023    # 正浮点数
-10, -221039210, -0.99         # 负数
8e-3, 4e6, -9e11               # 指数

在 lex_numbers函数里,如果得到数,最后返回的数是整数或者浮点形式

def lex_number(string):
    json_number = ''
    # 创建一个数字标准判断库,如果string中有数库中的字符,即为所需的number
    numbers_characters = [str(d) for d in range(1, 10)] + ['-', 'e', '.']

    for c in string:
        if c in numbers_characters:
            json_number += c
        else:
            break

    # 检查负号的位置是否合法
    if '-' in json_number:
        if 'e' in json_number:
            if json_number[0] is not '-' or json_number[json_number.find('e') + 1] is not '-':  # 负号只能在数的开头或者e的后面第一位
                raise Exception('发生解析错误')
        if json_number[0] is not '-':
            raise Exception('发生解析错误')

    # 获取的字符number有三个情况:1.整型;2.浮点;3.空
    if len(json_number) == 0:
        return None, string
    elif '.' in json_number:
        return float(json_number), string[len(json_number):]
    else:  # 整数
        if 'e' in json_number:
            a = int(json_number[:json_number.find('e')])
            b = int(json_number[json_number.find('e') + 1:])
            answer = a * pow(10, b)
        else:
            answer = int(json_number)
        return answer, string[len(json_number):]

Lexing boolens

检查顺序:字符串长度,字符串内容:true or false

在 lex_bool函数里,最后返回的是布尔值

def lex_bool(string):
    # bool: true, false
    string_len = len(string)

    if string_len >= TRUE_LEN and \     # 符合长度且是true
        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       # 否则返回空

Lexing nulls

检查顺序,字符串长度,字符串内容:null

在lex_null函数里,最后返回的是True(代表得到了null,于是就会向tokens中加入None)

def lex_null(string):
    # null: null
    string_len = len(string)

    if string_len >= NULL_LEN:
        if string[:NULL_LEN] == 'null':
            return True, string[NULL_LEN:]

    return None, string

Syntactic analysis

# 应用一个句法分析器
tokens = lex('{"foo": [1, 2, {"bar": 2}]}') 
assert_equal(tokens, ['{', 'foo', ':', '[', 1, ',', 2, '{', 'bar', ':', 2, '}', ']', '}']) 
assert_equal(parse(tokens), {'foo': [1, 2, {'bar': 2}]})

句法分析器是这样工作的:

        首先有通过lex(string)得到的标志词组,其次判断它们组成的表达式是否符合表达式要求

        简单来说:判断表达式合法与否

# 合法的表达式
  {}                    # 空
  {"example":1}         # key : value
  {"e2":[1,2,3,'a']}    # key : value(list)
# 不合法的表达式
  {                     # 缺少右大括号  
  {"e3":[1,'a',99 }     # 缺少右边中括号
  {"e4":{"f":0}          # 逗号后没有词

从上面的例子来看

有了词法分析器解析出的元组tokens后,接下来是句法分析了。作为一个句法分析器,首先最重要的就是能够判断句子是否合法(元组和字典),函数结构大致长这样:

def parse_array(tokens):
    return [], tokens

def parse_object(tokens):
    return {}, tokens

def parser(tokens):
    t = tokens[0]

    if t == JSON_LEFTBRACE:
        return parse_object(tokens)
    elif t == JSON_LEFTBRACKET:
        return parse_array(tokens)
    else:
        tokens = tokens[1:]         

Parsing arrays

一个合格的元组,要有 左括号 [ , 右括号 ],还有里面元素之间的逗号

[1, 2]    # 很好
[3,       # 很不好

进入parse_array 函数之前,已经判断过左中括号了,于是接下来就检查元素之间的逗号和末尾的右括号就行了,检查的时候一个一个添加进元组json_array之中就好了

要注意的是,元素可以是JSON格式的任何值:字典,元组,字符串,数字,布尔值,null。而用来解析并获取这些值的方法就是parse()了,所以

检查顺序:元素,逗号,元素,右中括号结束

:1. 元素后没有逗号 2. 元组末尾没有右中括号

def parse_array(tokens):
    json_array = []

    if tokens[0] == JSON_RIGHTBRACKET:
        return json_array, tokens[1:]

    while True:     # 顺序是:获得元素对象,添加元素;检查逗号或者右中括号,否则报错(元素后需要逗号)
        json, tokens = parser(tokens)
        json_array.append(json)

        t = tokens[0]
        if t == JSON_RIGHTBRACKET:
            return json_array, tokens[1:]
        elif t == JSON_COMMA:
            tokens = tokens[1:]
        else:
            raise Exception('Excepted comma after object')

    raise Exception('Excepted end-of-array bracket')

Parsing objects

一个合格的对象,要有 左括号 {, 右括号 },键和值中间的冒号: ,还有元素之间的逗号

要注意的是,键值只能是字符串类型,而键对应的值val可以是任何类型的

检查顺序:键,冒号,值,逗号,键,冒号,值,右大括号结束

:1. 键不是字符串 2. 键后没有冒号 3. 元素后没有逗号 4. 末尾没有右大括号

def parse_object(tokens):
    json_object = {}

    if tokens[0] == JSON_RIGHTBRACE:
        return json_object, tokens[1:]

    while True:
        json_key, tokens = parser(tokens)                     # 获取键
        key_type = type(json_key)                   
        if key_type != str:                                   # 哦 判断键类型,必须是str
            raise Exception('Key type must be str')

        t = tokens[0]
        if t != JSON_COLON:                                   # 检查键后是否有冒号
            return Exception('Expected colon after key in object, got: {}'.format(t))
        else:
            tokens = tokens[1:]

        json_val, tokens = parser(tokens)
        json_object[json_key] = json_val                      # 给键赋值

        t = tokens[0]
        if t == JSON_RIGHTBRACE:                              # 右大括号结束
            return json_object, tokens[1:]
        elif t == JSON_COMMA:
            tokens = tokens[1:]
        else:
            raise Exception('Excepted comma after object ')

    raise Exception('Excepted end-of-dict brace')

Unifying the library

为了提供一个理想的接口,用一个from_string 函数把把词法分析器和句法分析器封装起来就行啦

def from_string(string):
    tokens = lex(string)
    return parse(tokens)[0]      # 返回的是句法分析器 解析并合成 的一个**字典类型**的对象

这样子这个JSON文库就完成了

下面是测试代码,拿去测试你的工程吧!!

import unittest

import jsonpackage


class TestStringMethods(unittest.TestCase):
    def test_empty_object(self):
        self.assertEqual(jsonpackage.from_string('{}'), {})

    def test_basic_object(self):
        self.assertEqual(jsonpackage.from_string('{"foo":"bar"}'), {"foo": "bar"})

    def test_basic_number(self):
       self.assertEqual(jsonpackage.from_string('{"foo":11}'), {"foo": 11})

    def test_empty_array(self):
        self.assertEqual(jsonpackage.from_string('{"foo":[]}'), {"foo": []})

    def test_basic_array(self):
        self.assertEqual(jsonpackage.from_string('{"foo":[1,2,"three"]}'), {"foo": [1, 2, "three"]})

    def test_nested_object(self):
        self.assertEqual(jsonpackage.from_string('{"foo":{"bar":2}}'), {"foo": {"bar": 2}})

    def test_true(self):
        self.assertEqual(jsonpackage.from_string('{"foo":true}'), {"foo": True})

    def test_false(self):
        self.assertEqual(jsonpackage.from_string('{"foo":false}'), {"foo": False})

    def test_null(self):
        self.assertEqual(jsonpackage.from_string('{"foo":null}'), {"foo": None})

    def test_basic_whitespace(self):
        self.assertEqual(jsonpackage.from_string('{ "foo" : [1, 2, "three"] }'), {"foo": [1, 2, "three"]})



if __name__ == '__main__':
    unittest.main()

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要设计一个简单JSON解析器,你可以选择使用C语言来实现。以下是在C语言中设计一个简单JSON解析器需要的环境部署步骤: 1. 选择一个合适的开发环境 在编C语言代码时,你可以选择使用Windows、Linux或MacOS等操作系统,以及Visual Studio、Code::Blocks、Eclipse等集成开发环境。另外,你也可以使用文本编辑器和命令行编译器来编和编译C代码。 2. 了解JSON格式 在设计JSON解析器之前,你需要先了解JSON格式的语法和规则。JSON格式是一种轻量级的数据交换格式,常用于Web应用程序中。它基于JavaScript语法,但可以与多种编程语言一起使用。JSON格式有以下几种数据类型: - 对象(Object):由一组无序的键值对组成,使用花括号{}表示。 - 数组(Array):由一组有序的值组成,使用方括号[]表示。 - 字符串(String):由一组Unicode字符组成,使用双引号""或单引号''表示。 - 数字(Number):整数或浮点数。 - 布尔值(Boolean):true或false。 - 空值(Null):表示没有值。 3. 设计JSON解析算法 JSON解析器的核心算法是递归下降算法。该算法通过逐个读取JSON字符串的字符,解析出其中的语法结构,并将其转换为程序中的数据结构。具体实现过程中,可以使用栈来实现递归下降算法的非递归版本。 4. 编JSON解析器代码 在了解JSON格式和设计JSON解析算法之后,你可以开始编JSON解析器的代码。在C语言中,你可以使用标准库中的字符串处理函数和文件操作函数来实现JSON解析器。具体实现过程中,你需要定义一个数据结构来存储解析出来的JSON数据,同时编递归下降算法来解析JSON字符串并将其转换为程序中的数据结构。 5. 编译和测试代码 在编JSON解析器代码后,你需要使用C语言编译器将其编译成可执行文件。在命令行中进入代码所在的目录,使用gcc编译器将代码编译成可执行文件: ``` gcc -o json_parser json_parser.c ``` 然后运行生成的可执行文件,测试JSON解析器的功能是否符合预期。 至此,你已经完成了在C语言中设计一个简单JSON解析器的环境部署。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值