通过阅读 Douglas Crockford 的源码学习如何写 JSON parser(一)
JSON-js
Douglas Crockford 是 JSON 的发明者,所以通过 DC 的代码来学习 JSON 和 parser 绝对是上乘之选。这个仓库里面有四个 JS 文件,今天我们先研究 json_parse.js。
json_parse 定义了如下 API:
json_parse(string) => object
json_parse(string, (key,value)=>newValue ) => object
复制代码
今天我们只研究第一种 API。
代码结构
用 WebStorm 打开源码方便阅读,把主要函数折叠起来,就会发现代码结构非常清晰,完整结构如下:
var json_parse = (function(){'use strict'var at; // The index of the current charactervar ch; // The current charactervar escape = {...}var textvar error = function(){...}var next = function(){...}var number = function(){...}var string = function(){...}var white = function(){...}var word = function(){...}var array = function(){...}var object = function(){...}var value = function(){...}return function parser(source, reciver){...}
}())
复制代码
代码首先用一个立即执行函数造出一个局部作用域,ES 6 中我们只需要用 block 和 let 代替就行了。
思路
主要思路在最后一个 parser 函数里,我们来看一下:
return function (source, reviver) {var result;text = source;at = 0;ch = " ";result = value();white();if (ch) {error("Syntax error");}return result;
};
复制代码
看起来毫无逻辑呀。
为什么我老是说「看源码的投入产出比很低」呢,因为你需要看完所有代码,才知道主要逻辑是在做什么。
还好代码不多,我看完之后总结作者的思路如下。
有三个重要的变量,ch、at 和 text
- ch 指向一个字符(实际上是复制了字符的值,但是用指向更好理解源码),ch 默认指向一个空字符串(不要问这个空字符串有什么意义,主要是为了让代码简洁)
- at 指向下一个字符,at 存储了下一个字符的索引(index)
- text 包含了所有字符,也就是一个符合 JSON 语法的字符串
接下来我们定义一个动作:吃。
- 吃,表示将 ch 指向 at 所指的字符,然后 at 指向下一个字符。
- 吃一个空格,表示 ch 指向的字符必须是一个空格,然后吃(吃的定义见第一条);换句话说,吃一个空格的意思就是:我吃掉的字符必须是空格,不是空格就报错。
- 吃一个{,表示我吃掉的字符必须是{,否则就报错
- 吃一个},表示我吃掉的字符必须是},否则就报错
- 以此类推……
好了,parser 的难点讲完了,接下来就是细节了,假设 text 是字符串 { "name" : "Frank" },一次完整的逻辑如下
ch=" ",at=0, text='{ "name" : "Frank" }'
- 吃一个空格。由于 ch 一开始的默认值是空格,所以这个空格就被吃掉了,然后 ch 指向text 的第一个字符,at 指向 ch 后面一个字符(存下标,也就是1)。
- 如果 ch 是空格就继续吃,吃到 ch 不是空格为止。
- 发现 ch 是
{
,就说明这是一个对象,生成一个空对象 object 用来存储 key 和 value。而且后面的字符就要按照对象的语法来吃。 - 吃空格直到遇到非空格。理论上
{
后面应该接一个"key"
,所以这个非空格必须是"
。 - 吃一个
"
- 吃 N 个非
"
的字符(N >= 0) - 吃一个
"
- 把刚才吃到的 N 个字符作为一个 key,放到空对象 object 里
- 吃空格直到遇到非空格。理论上
"key"
后面应该接:
所以这个非空格必须是:
- 吃一个
:
- 吃空格直到遇到非空格。理论上冒号后面应该接 value,value 的值可以是对象、数组、字符串、bool、null 等,所以不能预期这个非空格是什么
- 发现是一个
"
,吃掉这个"
,如果值是一个字符串 - 吃 N 个非
"
的字符 - 吃一个
"
- 把刚才吃到的 N 个字符作为一个 value,放到空对象 object 里
- 吃空格直到遇到非空格。理论上 value 后面可以接逗号或者
}
- 发现 ch 是
}
,吃掉}
,说明 object 的数据已经读完了 - 一直吃空格,如果发现非空格,说明语法错误,报错。
- 将 object 返回,这个 object 就是 text 对应的数据了。
如果你能在大脑里过一遍这个过程,就可以看懂所有源码了:
var json_parse = (function(){'use strict'var at; // The index of the current charactervar ch; // The current charactervar escape = {...}var textvar error = function(){...}var next = 吃(){}var number = 吃一个完整的数字(){...}var string = 吃一个完整的字符串(){...}var white = 吃N个空格(){...}var word = 吃true/false/null这几个单词(){...}var array = 吃一个完整的字符串(){...}var object = 吃一个对象(){...}var value = 吃一个值,包括对象数组字符串数组bool和null(){...}return function parser(source, reciver){...}
}())
复制代码
然后我们就可以重点看主逻辑了:
return function (source, reviver) {var result;text = source;at = 0;ch = " ";result = value(); // 吃一个值white(); // 吃掉后面的空格if (ch) { // 如果空格后面还有字符,就是语法错误了error("Syntax error");}return result;
};
复制代码
也就是说主逻辑其实很简单
- 用 value() 吃一个值,这个值就是 text 对应的数据
- 继续吃掉所有空格
- 吃完发现还有字符(一定是非空格),就说明语法错了(画蛇添足)
接下来我们看 value() 的逻辑
value = function () {white();switch (ch) {case "{":return object();case "[":return array();case "\"":return string();case "-":return number();default:return (ch >= "0" && ch <= "9")? number(): word();}
};
复制代码
逻辑也很简单:
- 吃掉所有空格。
- 看当前的字符(ch)是什么
- 如果 ch 是
{
,就吃一整个对象,然后把对象返回 - 如果 ch 是
[
,就吃一整个数组,然后把数组返回 - 如果 ch 是
"
,就吃一整个字符串,然后把字符串返回 - 如果 ch 是
-
,就吃一整个数字,然后把数字返回 - 如果 ch 是
0
~9
,就吃一整个数字,然后把数字返回 - 其他情况只可能是
true
/false
/null
,见啥吃啥,然后返回
图示如下:
DC 用 ch >= "0" && ch <= "9"
来判断字符是不是 0~9,这用到了 ASCII 字符集,如果你不懂就去搜一下。
大家应该对如何吃一个对象最感兴趣,我们来看看 object() 的逻辑
var object = function () {var key;var obj = {};if (ch === "{") { // 当前字符必然是 {next("{"); // 吃掉这个 {white(); // 吃掉所有空格if (ch === "}") { // 遇到 } 说明对象结束了next("}"); // 吃掉这个 }return obj; // 返回空对象}while (ch) { // 没有遇到 } 说明有 keykey = string(); // 吃一个 string 当做 keywhite(); // 吃掉所有空格next(":"); // 吃掉一个 :if (Object.hasOwnProperty.call(obj, key)) {error("Duplicate key '" + key + "'");} // 如果这个 key 之前遇到过就报错obj[key] = value();// 把key当做object的key,然后吃一个value作为值white(); // 吃掉所有空格if (ch === "}") { // 如果遇到 } 说明对象结束了next("}"); // 吃掉这个 }return obj; // 返回对象}next(","); // 没有遇到 } 说明还有 key,吃一个逗号white(); // 吃掉空格然后继续回到上面吃 key}}error("Bad object"); // 如果运行到这里说明语法有问题
};
复制代码
到此我们基本搞清楚 DC 的 json_parser 的思路了,大家可以自己看一下 white()
、array()
的源码,结构十分清晰。
下次我们讲 json_parse_state.js 如何使用状态机的思路重写了这个 parser。
我的微信公众号:搜索 XDML 四个字母即可,XDML 是「写代码啦」的拼音首字母。
通过阅读 Douglas Crockford 的源码学习如何写 JSON parser(一)相关推荐
- 小说阅读Autojs源码学习
"ui"; var rootUrl = ""; var storaySign = ""; var woolStorage = storage ...
- DotText源码学习——ASP.NET的工作机制
--本文是<项目驱动学习--DotText源码学习>系列的第一篇文章,在这之后会持续发表相关的文章. 概论 在阅读DotText源码之前,让我们首先了解一下ASP.NET的工作机制,可以使 ...
- Java 源码学习系列(三)——Integer
Integer 类在对象中包装了一个基本类型 int 的值.Integer 类型的对象包含一个 int 类型的字段. 此外,该类提供了多个方法,能在 int 类型和 String 类型之间互相转换,还 ...
- Deep Compression阅读理解及Caffe源码修改
Deep Compression阅读理解及Caffe源码修改 作者:may0324 更新: 没想到这篇文章写出后有这么多人关注和索要源码,有点受宠若惊.说来惭愧,这个工作当时做的很粗糙,源码修改的比 ...
- java-HashMap源码学习
阅读提示:HashMap源码在不同版本情况下,具体源码可能不一样(优化问题),但功能几乎是相同的(博主1.8) 什么是Hash? hash表是一种数据结构,它拥有惊人的效率,它的时间复杂度低到接近O( ...
- vue源码学习--vue源码学习入门
本文为开始学习vue源码的思路整理.在拿到vue项目源码的之后看到那些项目中的文件夹,会很困惑,不知道每个文件夹内的世界,怎么变换,怎样的魔力,最后产生了vue框架.学习源码也无从学起.我解决了这些困 ...
- java Integer 源码学习
转载自http://www.hollischuang.com/archives/1058 Integer 类在对象中包装了一个基本类型 int 的值.Integer 类型的对象包含一个 int 类型的 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)(转)
阅读目录 一.MVC原理解析 1.MVC原理 二.HttpHandler 1.HttpHandler.IHttpHandler.MvcHandler的说明 2.IHttpHandler解析 3.Mvc ...
- RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的?
RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 文章目录 RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 前言 项目 ...
最新文章
- 导入sql时出现Invalid default value for ‘create_time‘报错处理方法
- ppwjs之bootstrap文字排版:排版常量
- 2020/Province_C_C++_A/F/成绩分析
- 使用Nexus搭建私有Nuget仓库
- c语言gets与fgetc,区分C语言中getch、getche、fgetc、getc、getchar、fgets、gets 转
- 西门子array数据类型_西门子S71200之间以太网通信(图文)
- 垃圾邮件过滤——学习笔记
- [2013.8.29]对于多线程编程的几点个人见解
- python电商用户购买力分析_Python + pandas + 不同客户购买力图形显示
- 微信公众开放平台开发03---百度BAE上搭建属于自己的微信公众平台 -JAVA,微信公众开放平台部署到百度云中BASE2.0,进行调试,木有钱买云服务器的亲们试试
- BluetoothLE-Multi-Library 一个能够连接多台蓝牙设备的库,它可以作为client端,也可以为server端。支持主机/从机,外围设备连接。...
- ENVI实验教程(8)实验八、高光谱与光谱分析
- H5调用摄像头拍照并下载照片
- Unity 导入高分辨率图片
- 虚拟试衣是什么及优势在哪
- 非度量多维标度_用R语言做非度量多维尺度分析(NMDS)
- 计算机科学的刊物卷号,期刊的卷号和期号怎么看
- 一个非常好用的图片切割工具(c# winform开发)
- html图片用什么软件打开,.svg是什么文件 用什么软件打开
- eclipes安装lombok