通过阅读 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" },一次完整的逻辑如下

  1. ch=" ",at=0, text='{ "name" : "Frank" }'
  2. 吃一个空格。由于 ch 一开始的默认值是空格,所以这个空格就被吃掉了,然后 ch 指向text 的第一个字符,at 指向 ch 后面一个字符(存下标,也就是1)。
  3. 如果 ch 是空格就继续吃,吃到 ch 不是空格为止。
  4. 发现 ch 是 {,就说明这是一个对象,生成一个空对象 object 用来存储 key 和 value。而且后面的字符就要按照对象的语法来吃。
  5. 吃空格直到遇到非空格。理论上 { 后面应该接一个 "key",所以这个非空格必须是 "
  6. 吃一个 "
  7. 吃 N 个非 " 的字符(N >= 0)
  8. 吃一个 "
  9. 把刚才吃到的 N 个字符作为一个 key,放到空对象 object 里
  10. 吃空格直到遇到非空格。理论上 "key" 后面应该接 : 所以这个非空格必须是 :
  11. 吃一个 :
  12. 吃空格直到遇到非空格。理论上冒号后面应该接 value,value 的值可以是对象、数组、字符串、bool、null 等,所以不能预期这个非空格是什么
  13. 发现是一个 ",吃掉这个 ",如果值是一个字符串
  14. 吃 N 个非 " 的字符
  15. 吃一个 "
  16. 把刚才吃到的 N 个字符作为一个 value,放到空对象 object 里
  17. 吃空格直到遇到非空格。理论上 value 后面可以接逗号或者 }
  18. 发现 ch 是 },吃掉 },说明 object 的数据已经读完了
  19. 一直吃空格,如果发现非空格,说明语法错误,报错。
  20. 将 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;
};
复制代码

也就是说主逻辑其实很简单

  1. 用 value() 吃一个值,这个值就是 text 对应的数据
  2. 继续吃掉所有空格
  3. 吃完发现还有字符(一定是非空格),就说明语法错了(画蛇添足)

接下来我们看 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();}
};
复制代码

逻辑也很简单:

  1. 吃掉所有空格。
  2. 看当前的字符(ch)是什么
  3. 如果 ch 是 {,就吃一整个对象,然后把对象返回
  4. 如果 ch 是 [,就吃一整个数组,然后把数组返回
  5. 如果 ch 是 ",就吃一整个字符串,然后把字符串返回
  6. 如果 ch 是 -,就吃一整个数字,然后把数字返回
  7. 如果 ch 是 0~9,就吃一整个数字,然后把数字返回
  8. 其他情况只可能是 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 是「写代码啦」的拼音首字母。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/254874.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

halcon gen_region_hline 绘制霍夫变换提取直线

目录gen_region_hline&#xff08;算子&#xff09;描述gen_region_hline&#xff08;算子&#xff09; gen_region_hline - 将霍夫变换提取直线以普通形式描述的输入行存储为区域。 gen_region_hline( : Regions : Orientation, Distance : ) 描述 运算符gen_region_hline…

H.264视频RTP负载格式/NALU的类型

1. 网络抽象层单元类型(NALU) NALU 头由一个字节组成, 它的语法如下: |0|1|2|3|4|5|6|7| |F|NRI| Type | F: 1 个比特. forbidden_zero_bit&#xff1a;在H.264规范中规定了这一位必须为0。 NRI: 2 个比特. nal_ref_idc&#xff1…

4.10下午

转载于:https://www.cnblogs.com/yanyuying/p/6690662.html

虚函数

1、基于向上类型转换&#xff0c;基类通过虚函数可以对多个子类相似的功能实现统一管理。 2、例子&#xff1a; A为基类&#xff0c;B、C为A的同级子类。virtual只需在基类中标识一次&#xff0c;子类无需重复标识。class A {virtual void display(){cout<<"我是A&q…

Android绘制(一):来用shape绘出想要的图形吧!

目录 前言shape绘制矩形椭圆线环用shape绘制SeekBar最后前言 在没有UI设计师的时候, 或者是想简单看下效果的时候, 用shape进行快速绘制是极好的! 官方文档. shape绘制 一共有四种shape: rectangle, oval, line, ring. 矩形 我们一个一个来看, 首先是矩形: <?xml version&q…

halcon sobel 边缘检测 sobel_dir

目录sobel_dir&#xff08;算子&#xff09;描述参数sobel_dir&#xff08;算子&#xff09; sobel_dir - 使用Sobel算子检测边缘&#xff08;振幅和方向&#xff09;。 sobel_dir(Image : EdgeAmplitude, EdgeDirection : FilterType, Size : ) 描述 sobel_dir计算图像的一…

静态链接库LIB和动态链接库DLL的区别 创建和示例

1.什么是静态连接库&#xff0c;什么是动态链接库静态链接库与动态链接库都是共享代码的方式&#xff0c;如果采用静态链接库&#xff0c;则无论你愿不愿意&#xff0c; lib 中的指令都全部被直接包含在最终生成的 EXE 文件中了。 但是若使用 DLL&#xff0c;该 DLL 不必被包含…

【译】x86程序员手册37-第10章 初始化

Chapter 10 Initialization 第10章 初始化 After a signal on the RESET pin, certain registers of the 80386 are set to predefined values. These values are adequate to enable execution of a bootstrap program, but additional initialization must be performed by s…

在ubuntu中安装minicom时出现device /dev/tty8 is locked解决办法

未正常关闭minicom yesaiduywf-ubuntu: ~$ ls /var/lock LCK..ttyS0 subsys yesaiduywf-ubuntu: ~$ kill 0 yesaiduywf-ubuntu: ~$ ls /var/lock subsys yesaiduywf-ubuntu: ~$ sudo minicom Welcome to minicom 2.3 或者删除/var/lock下面以LCK开头的文件转载于:https://www.…

秒杀多线程第二篇 多线程第一次亲密接触 CreateThread与_beginthreadex本质区别

本文将带领你与多线程作第一次亲密接触&#xff0c;并深入分析CreateThread与_beginthreadex的本质区别&#xff0c;相信阅读本文后你能轻松的使用多线程并能流畅准确的回答CreateThread与_beginthreadex到底有什么区别&#xff0c;在实际的编程中到底应该使用CreateThread还是…

halcon get_image_pointer1获取图像指针

目录get_image_pointer1&#xff08;算子&#xff09;描述参数get_image_pointer1&#xff08;算子&#xff09; get_image_pointer1 - 访问单通道图像的指针。 get_image_pointer1(Image : : : Pointer, Type, Width, Height) 描述 运算符get_image_pointer1返回指向图像I…

如何传输文件到linux服务器?

我们知道&#xff0c;云主机文件传输是一件相对复杂的事情&#xff0c;经常需要搭建FTP服务器或者是借助其他工具来完成。下面为大家介绍一种简单易操作的传输文件到Linux服务器的方法。 Linux文件传输同Windows文件传输一样&#xff0c;我们为每一台Linux主机配置了一个1G的网…

C++学习笔记(五)--指针、NULL、引用

1. C中已经定义了NULL为0:#define NULL 0 指针p可以指向空值NULL即 p NULL;表示该指针变量不指向任何变量。   注意&#xff1a;指针未初始化与指针为NULL不同&#xff0c;   p NULL;是有值的&#xff0c;为0&#xff1b;   而在定义时&#xff1a;int *p;这时候碎虽然…

Sort函数的用法

快速排序sort的用法&#xff1a;&#xff08;适用于int float double char 。。。&#xff09; 记得加头文件&#xff01; 记得加头文件&#xff01; 记得加头文件&#xff01; 头文件&#xff1a; #include <algorithm> using namespace std ; // 两行都要写 数组排…

crf与bitrate对照表

crf与bitrate对照表 (2011-06-21 17:45:59)一些关于crf的备忘&#xff1a; 1、相较于bitrate方式&#xff0c;cpu占用与内存占用均会下降&#xff1b; 2、锐化滤镜会让crf的码率上升&#xff1b; 3、vbv对crf依然有效&#xff1b; 4、crf18就接近无损&#xff0c;字幕组惯用20-…

秒杀多线程第三篇 原子操作 Interlocked系列函数

上一篇《多线程第一次亲密接触 CreateThread与_beginthreadex本质区别》中讲到一个多线程报数功能。为了描述方便和代码简洁起见&#xff0c;我们可以只输出最后的报数结果来观察程序是否运行出错。这也非常类似于统计一个网站每天有多少用户登录&#xff0c;每个用户登录用一个…

Vue 教程第九篇—— 动画和过度效果

过渡效果 SPA 中组件的切换有一种生硬的隐藏显示感觉&#xff0c;为了更好的用户体验&#xff0c;让组件切换时淡出淡入&#xff0c;Vue 提供了专门的组件 transition。 过滤效果应用场景 条件渲染 (使用 v-if)条件展示 (使用 v-show)动态组件组件根节点过渡状态 enter&#xf…

halcon create_ocr_class_svm 使用SVM分类器创建OCR分类器

目录create_ocr_class_svm&#xff08;算子&#xff09;描述参数create_ocr_class_svm&#xff08;算子&#xff09; create_ocr_class_svm - 使用支持随机向量机制创建OCR分类器。 create_ocr_class_svm&#xff08;:: WidthCharacter&#xff0c;HeightCharacter&#xff0…

码率跟视频质量有关系

码率跟视频质量有关系.首先要清楚, 相同的视频编码方式下, 码率越高肯定画面越清晰. 但是高到一定值, 再往上的画面改善程度就不明显了, 只会增大文件体积. 所以码率选的合适, 才可以保证清晰度又保持文件不会太大. 个人经验如果是h.264编码(当前最好的视频压缩编码方案), …

SQL 字符串分割表函数

1 --字符串分割表函数2 declare str varchar(1000)3 declare split varchar(10) 4 5 declare i int;6 declare count int;7 8 declare ChildStr varchar(1000);9 declare splitStr varchar(1000); 10 declare Index int; 11 12 declare table as table (rowId int,splitStr va…