编写一个JSON解析器实际上就是一个函数,它的输入是一个表示JSON的字符串,输出是结构化的对应到语言本身的数据结构。

和XML相比,JSON本身结构非常简单,并且仅有几种数据类型,以Java为例,对应的数据结构是:

  • "string":Java的String

  • number:Java的LongDouble

  • true/false:Java的Boolean

  • null:Java的null

  • [array]:Java的List<Object>Object[]

  • {"key":"value"}:Java的Map<String, Object>

解析JSON和解析XML类似,最终都是解析为内存的一个对象。出于效率考虑,使用流的方式几乎是唯一选择,也就是解析器只从头扫描一遍JSON字符串,就完整地解析出对应的数据结构。

本质上解析器就是一个状态机,只要按照JSON定义的格式(参考http://www.json.org,正确实现状态转移即可。但是为了简化代码,我们也没必要完整地实现一个字符一个字符的状态转移。

解析器的输入应该是一个字符流,所以,第一步是获得Reader,以便能不断地读入下一个字符。

在解析的过程中,我们经常要根据下一个字符来决定状态跳转,此时又涉及到回退的问题,就是某些时候不能用next()取下一个字符,而是用peek()取下一个字符,但字符流的指针不移动。所以,Reader接口不能满足这个需求,应当进一步封装一个CharReader,它可以实现:

  • char next():读取下一个字符,移动Reader指针;

  • char peek():读取下一个字符,不移动Reader指针;

  • String next(int size):读取指定的N个字符并移动指针;

  • boolean hasMore():判断流是否结束。

JSON解析比其他文本解析要简单的地方在于,任何JSON数据类型,只需要根据下一个字符即可确定,仔细总结可以发现,如果peek()返回的字符是某个字符,就可以期望读取的数据类型:

  • {:期待一个JSON object;

  • ::期待一个JSON object的value;

  • ,:期待一个JSON object的下一组key-value,或者一个JSON array的下一个元素;

  • [:期待一个JSON array;

  • t:期待一个true;

  • f:期待一个false;

  • n:期待一个null;

  • ":期待一个string;

  • 0~9:期待一个number。

但是单个字符要匹配的状态太多了,需要进一步把字符流变为Token,可以总结出如下几种Token

  • END_DOCUMENT:JSON文档结束;

  • BEGIN_OBJECT:开始一个JSON object;

  • END_OBJECT:结束一个JSON object;

  • BEGIN_ARRAY:开始一个JSON array;

  • END_ARRAY:结束一个JSON array;

  • SEP_COLON:读取一个冒号;

  • SEP_COMMA:读取一个逗号;

  • STRING:一个String;

  • BOOLEAN:一个true或false;

  • NUMBER:一个number;

  • NULL:一个null。

然后,将CharReader进一步封装为TokenReader,提供以下接口:

  • Token readNextToken():读取下一个Token;

  • boolean readBoolean():读取一个boolean;

  • Number readNumber():读取一个number;

  • String readString():读取一个string;

  • void readNull():读取一个null。

由于JSON的Object和Array可以嵌套,在读取过程中,使用一个栈来存储Object和Array是必须的。每当我们读到一个BEGIN_OBJECT时,就创建一个Map并压栈;每当读到一个BEGIN_ARRAY时,就创建一个List并压栈;每当读到一个END_OBJECTEND_ARRAY时,就弹出栈顶元素,并根据新的栈顶元素判断是否压栈。此外,读到Object的Key也必须压栈,读到后面的Value后将Key-Value压入栈顶的Map。

如果读到END_DOCUMENT时,栈恰好只剩下一个元素,则读取正确,将该元素返回,读取结束。如果栈剩下不止一个元素,则JSON文档格式不正确。

最后,JsonReader的核心解析代码parse()就是负责从TokenReader中不断读取Token,根据当前状态操作,然后设定下一个Token期望的状态,如果与期望状态不符,则JSON的格式无效。起始状态被设定为STATUS_EXPECT_SINGLE_VALUE | STATUS_EXPECT_BEGIN_OBJECT | STATUS_EXPECT_BEGIN_ARRAY,即期望读取到单个value、{[。循环的退出点是读取到END_DOCUMENT时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
public class JsonReader {
  
    TokenReader reader;
  
    public Object parse() {
        Stack stack = new Stack();
        int status = STATUS_EXPECT_SINGLE_VALUE | STATUS_EXPECT_BEGIN_OBJECT | STATUS_EXPECT_BEGIN_ARRAY;
        for (;;) {
            Token currentToken = reader.readNextToken();
            switch (currentToken) {
            case BOOLEAN:
                if (hasStatus(STATUS_EXPECT_SINGLE_VALUE)) {
                    // single boolean:
                    Boolean bool = reader.readBoolean();
                    stack.push(StackValue.newJsonSingle(bool));
                    status = STATUS_EXPECT_END_DOCUMENT;
                    continue;
                }
                if (hasStatus(STATUS_EXPECT_OBJECT_VALUE)) {
                    Boolean bool = reader.readBoolean();
                    String key = stack.pop(StackValue.TYPE_OBJECT_KEY).valueAsKey();
                    stack.peek(StackValue.TYPE_OBJECT).valueAsObject().put(key, bool);
                    status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_OBJECT;
                    continue;
                }
                if (hasStatus(STATUS_EXPECT_ARRAY_VALUE)) {
                    Boolean bool = reader.readBoolean();
                    stack.peek(StackValue.TYPE_ARRAY).valueAsArray().add(bool);
                    status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_ARRAY;
                    continue;
                }
                throw new JsonParseException("Unexpected boolean.", reader.reader.readed);
  
            case NULL:
                if (hasStatus(STATUS_EXPECT_SINGLE_VALUE)) {
                    // single null:
                    reader.readNull();
                    stack.push(StackValue.newJsonSingle(null));
                    status = STATUS_EXPECT_END_DOCUMENT;
                    continue;
                }
                if (hasStatus(STATUS_EXPECT_OBJECT_VALUE)) {
                    reader.readNull();
                    String key = stack.pop(StackValue.TYPE_OBJECT_KEY).valueAsKey();
                    stack.peek(StackValue.TYPE_OBJECT).valueAsObject().put(key, null);
                    status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_OBJECT;
                    continue;
                }
                if (hasStatus(STATUS_EXPECT_ARRAY_VALUE)) {
                    reader.readNull();
                    stack.peek(StackValue.TYPE_ARRAY).valueAsArray().add(null);
                    status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_ARRAY;
                    continue;
                }
                throw new JsonParseException("Unexpected null.", reader.reader.readed);
  
            case NUMBER:
                if (hasStatus(STATUS_EXPECT_SINGLE_VALUE)) {
                    // single number:
                    Number number = reader.readNumber();
                    stack.push(StackValue.newJsonSingle(number));
                    status = STATUS_EXPECT_END_DOCUMENT;
                    continue;
                }
                if (hasStatus(STATUS_EXPECT_OBJECT_VALUE)) {
                    Number number = reader.readNumber();
                    String key = stack.pop(StackValue.TYPE_OBJECT_KEY).valueAsKey();
                    stack.peek(StackValue.TYPE_OBJECT).valueAsObject().put(key, number);
                    status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_OBJECT;
                    continue;
                }
                if (hasStatus(STATUS_EXPECT_ARRAY_VALUE)) {
                    Number number = reader.readNumber();
                    stack.peek(StackValue.TYPE_ARRAY).valueAsArray().add(number);
                    status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_ARRAY;
                    continue;
                }
                throw new JsonParseException("Unexpected number.", reader.reader.readed);
  
            case STRING:
                if (hasStatus(STATUS_EXPECT_SINGLE_VALUE)) {
                    // single string:
                    String str = reader.readString();
                    stack.push(StackValue.newJsonSingle(str));
                    status = STATUS_EXPECT_END_DOCUMENT;
                    continue;
                }
                if (hasStatus(STATUS_EXPECT_OBJECT_KEY)) {
                    String str = reader.readString();
                    stack.push(StackValue.newJsonObjectKey(str));
                    status = STATUS_EXPECT_COLON;
                    continue;
                }
                if (hasStatus(STATUS_EXPECT_OBJECT_VALUE)) {
                    String str = reader.readString();
                    String key = stack.pop(StackValue.TYPE_OBJECT_KEY).valueAsKey();
                    stack.peek(StackValue.TYPE_OBJECT).valueAsObject().put(key, str);
                    status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_OBJECT;
                    continue;
                }
                if (hasStatus(STATUS_EXPECT_ARRAY_VALUE)) {
                    String str = reader.readString();
                    stack.peek(StackValue.TYPE_ARRAY).valueAsArray().add(str);
                    status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_ARRAY;
                    continue;
                }
                throw new JsonParseException("Unexpected char \'\"\'.", reader.reader.readed);
  
            case SEP_COLON: // :
                if (status == STATUS_EXPECT_COLON) {
                    status = STATUS_EXPECT_OBJECT_VALUE | STATUS_EXPECT_BEGIN_OBJECT | STATUS_EXPECT_BEGIN_ARRAY;
                    continue;
                }
                throw new JsonParseException("Unexpected char \':\'.", reader.reader.readed);
  
            case SEP_COMMA: // ,
                if (hasStatus(STATUS_EXPECT_COMMA)) {
                    if (hasStatus(STATUS_EXPECT_END_OBJECT)) {
                        status = STATUS_EXPECT_OBJECT_KEY;
                        continue;
                    }
                    if (hasStatus(STATUS_EXPECT_END_ARRAY)) {
                        status = STATUS_EXPECT_ARRAY_VALUE | STATUS_EXPECT_BEGIN_ARRAY | STATUS_EXPECT_BEGIN_OBJECT;
                        continue;
                    }
                }
                throw new JsonParseException("Unexpected char \',\'.", reader.reader.readed);
  
            case END_ARRAY:
                if (hasStatus(STATUS_EXPECT_END_ARRAY)) {
                    StackValue array = stack.pop(StackValue.TYPE_ARRAY);
                    if (stack.isEmpty()) {
                        stack.push(array);
                        status = STATUS_EXPECT_END_DOCUMENT;
                        continue;
                    }
                    int type = stack.getTopValueType();
                    if (type == StackValue.TYPE_OBJECT_KEY) {
                        // key: [ CURRENT ] ,}
                        String key = stack.pop(StackValue.TYPE_OBJECT_KEY).valueAsKey();
                        stack.peek(StackValue.TYPE_OBJECT).valueAsObject().put(key, array.value);
                        status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_OBJECT;
                        continue;
                    }
                    if (type == StackValue.TYPE_ARRAY) {
                        // xx, xx, [CURRENT] ,]
                        stack.peek(StackValue.TYPE_ARRAY).valueAsArray().add(array.value);
                        status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_ARRAY;
                        continue;
                    }
                }
                throw new JsonParseException("Unexpected char: \']\'.", reader.reader.readed);
  
            case END_OBJECT:
                if (hasStatus(STATUS_EXPECT_END_OBJECT)) {
                    StackValue object = stack.pop(StackValue.TYPE_OBJECT);
                    if (stack.isEmpty()) {
                        // root object:
                        stack.push(object);
                        status = STATUS_EXPECT_END_DOCUMENT;
                        continue;
                    }
                    int type = stack.getTopValueType();
                    if (type == StackValue.TYPE_OBJECT_KEY) {
                        String key = stack.pop(StackValue.TYPE_OBJECT_KEY).valueAsKey();
                        stack.peek(StackValue.TYPE_OBJECT).valueAsObject().put(key, object.value);
                        status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_OBJECT;
                        continue;
                    }
                    if (type == StackValue.TYPE_ARRAY) {
                        stack.peek(StackValue.TYPE_ARRAY).valueAsArray().add(object.value);
                        status = STATUS_EXPECT_COMMA | STATUS_EXPECT_END_ARRAY;
                        continue;
                    }
                }
                throw new JsonParseException("Unexpected char: \'}\'.", reader.reader.readed);
  
            case END_DOCUMENT:
                if (hasStatus(STATUS_EXPECT_END_DOCUMENT)) {
                    StackValue v = stack.pop();
                    if (stack.isEmpty()) {
                        return v.value;
                    }
                }
                throw new JsonParseException("Unexpected EOF.", reader.reader.readed);
  
            case BEGIN_ARRAY:
                if (hasStatus(STATUS_EXPECT_BEGIN_ARRAY)) {
                    stack.push(StackValue.newJsonArray(this.jsonArrayFactory.createJsonArray()));
                    status = STATUS_EXPECT_ARRAY_VALUE | STATUS_EXPECT_BEGIN_OBJECT | STATUS_EXPECT_BEGIN_ARRAY | STATUS_EXPECT_END_ARRAY;
                    continue;
                }
                throw new JsonParseException("Unexpected char: \'[\'.", reader.reader.readed);
  
            case BEGIN_OBJECT:
                if (hasStatus(STATUS_EXPECT_BEGIN_OBJECT)) {
                    stack.push(StackValue.newJsonObject(this.jsonObjectFactory.createJsonObject()));
                    status = STATUS_EXPECT_OBJECT_KEY | STATUS_EXPECT_BEGIN_OBJECT | STATUS_EXPECT_END_OBJECT;
                    continue;
                }
                throw new JsonParseException("Unexpected char: \'{\'.", reader.reader.readed);
            }
        }
    }
}

如何编写一个JSON解析器的更多相关文章

  1. 一起写一个JSON解析器

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

  2. 几百行代码实现一个 JSON 解析器

    前言 之前在写 gscript时我就在想有没有利用编译原理实现一个更实际工具?毕竟真写一个语言的难度不低,并且也很难真的应用起来. 一次无意间看到有人提起 JSON 解析器,这类工具充斥着我们的日常开 ...

  3. 一个JSON解析器

    来源 <JavaScript语言精粹(修订版)> 代码 <!DOCTYPE html> <html> <head> <meta charset=& ...

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

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

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

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

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

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

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

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

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

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

  9. 一个简单的json解析器

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

随机推荐

  1. DOM Style样式对象的详细用法

    DOM Style样式对象的详细用法 HTML Style样式比较复杂,相应访问.修改方法也有所差异.参考相关资料,整理如下. 典型Html文件如下,有三种定义方式. <head>     ...

  2. C# 自定义线程修改UI(一)

    在Wpf中界面显示数据的修改,都是通过UI线程完成,如果尝试从其他线程中直接修改控件的值回抛出异常,“调用线程无法访问此对象,因为另一个线程拥有该对象”. 例如:http://www.cnblogs. ...

  3. 关于mtk Android打开串口权限问题

    最近在做一个测试串口读写回路的APK,jni代码部分遇到一个小小问题: fd = open(path_utf, O_RDWR);返回值是-1,要么就是权限问题,要么就是文件不存在所以需要打印错误信息, ...

  4. 关于模板中的动态取值 ---反射与javascript脚本编译

    在项目中经常遇到一个问题,打印word或者打印excel的时候,我们经常使用一对一的赋值或者批量替换的方式来对模板进行修改. 但是现在遇到两种场景: 1.取值是通过自定以方法进行取值的. 如:一个销售 ...

  5. Hibernate 性能优化之懒加载

    针对数据库中的大数据,不希望特别早的加载到内存中,当用到它的时候才加载 懒加载分为:类的懒加载.集合的懒加载.单端关联的懒加载 类的懒加载    1.在默认情况下,类就是执行懒加载        2. ...

  6. When Colon Scripting is comming(JavaScript语法扩充)

    当冒号脚本来临-- JavaScript语法扩充 连续好几夜的不能安眠,得出结论就是,未来语言未来的编译器应该是支持语法定制规则和语法扩展的,这样使得编程语言不断进化以更利于人类使用!of cours ...

  7. JavaScript 框架比较

    显著增强 JavaScript 开发的框架概览 Joe Lennon, 软件开发人员, 自由职业者 简介: 现代 Web 站点和 Web 应用程序倾向于依赖大量客户端 JavaScript 来提供丰富 ...

  8. Linux下python3与python3的多版本共存

    python3已经出来有些许时候了,python3相比python2进行了大量的改进,包括语法,新的功能,还有优化.虽然很多库已经同时支持 python2和python3了,但是有些库仍然没有很好的支 ...

  9. JSON基础知识总结

    JSON基础 一.JSON简介 JSON,全称“JavaScript Object Notation(JavaScript对象表示法)”,起源于JavaScript的对象和数组.JSON,说白了就是J ...

  10. jQuery 知识积累

    1.select下拉框设置选中项 //设置下拉框第一项为选中项$("#selectId option:first").prop("selected", 'sel ...