手写Json解析器学习心得

噢~从"{"开始,看来是个对象了!

一. 介绍

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

知乎回答---连接github

该回答对应的问题说起了一个开源的“从零开始的JSON库教程”,刚好我刚开始学习go语言,对Json的理解也仅停留在一种端到端之间交互的数据格式,因而便跟着教程写了一遍,受益良多,至少对我这种编程经验少的人来讲十分有帮助,如下是个人学习心得。golang

从零开始的JSON库教程地址---连接编程

本身的实现---连接json

二. 整体收获

1. 测试与重构

其实在刚开始接触编程的时候,也常常据说要给本身的代码写测试,可是一直没有学过相关的方法论,也不知道如何实践,直到在公司实习的时候才慢慢意识到测试的重要性。当时每写完一个功能,导师都会要求我造数据进行测试,从我当时的理解上看,本身写测试用例的目的在于尽可能去覆盖用户的各类行为,保证系统运行的稳定性。数组

可是在经历了这门Json解析器教程后,我对编写测试用例又有了更进一步的理解。该教程详细地介绍了一种叫TDD的开发模式,中文是测试驱动开发,并从第一单元开始就贯彻执行。安全

在我看来,先写测试后进行开发能帮助咱们明确咱们想要开发的功能,减小咱们走弯路的可能性。但有时提早作计划每每不太容易,可能会出现测试不太好写的状况,这个时候咱们先把功能开发出来反而会更轻松一些。教程做者也推荐咱们在实际开发中两种风格并用,以达到平衡。数据结构

说实话,刚开始看到本身的代码能顺利经过所有测试的时候还蛮有成就感的。可是随着课程的深刻,我发现完备的测试不仅是给我成就感这么简单,更多的是一种安全感。框架

由于随着解析器的功能增长,咱们的代码会出现一些通用的模块,为了提升通用性,咱们须要进行重构,而完备的单元测试是咱们放胆去重构的重要保障。函数

另外,因为和教程使用的语言不同,有些地方须要按本身的理解去写,不可以全盘照搬,许多地方一开始实现得不太周全。我印象最深的地方是一开始咱们要解析null,false,true,数字和字符串,这些都是单个功能,各自经过单独的测试用例不会很难。可是当咱们要解析数组的时候,因为数组中有多个值,并且还可能有嵌套数组,这个时候就要保证单个值的解析不影响全局的解析。

我当时在作数组解析的时候遇到了很多的问题,基本都是单值解析的代码不够完善而致使的。还好以前跟着教程写了足够的测试用例,支撑着我把整个数组解析功能写正确,今后爱上写单元测试。

2. C语言的魅力

教程是用标准的C语言写的,做者自己是C/C++的大牛,功力深厚。虽然我对C语言了解很少,可是跟着教程的解释去阅读C代码也没有太大的问题。

教程关于C语言的知识点不少,好比宏的定义,内存的分配与释放,内存泄漏检测等,最令我赞叹的是做者对指针的运用,太精巧了。虽然Go语言里面也有指针,可是在我作这个教程的过程当中,Go的指针更多时候只是用来传址。也因为指针没那么强大,我不太方便像做者同样实现一个通用强大的堆栈,用于暂存Json的解析内容。但Go语言有强大的Slices,用起来也很爽,很方便。

既然是用不一样的语言实现一样的功能,那咱们确定要充分发挥本身所用语言的优点了,这也是咱们想经过项目入门一门语言的关键。

三. 项目各个阶段的收获

1. 启程

开始的第一章中我最大的收获就是弄清楚了整个解析器的结构。

在项目的开始阶段,咱们首先搭建一个简单测试框架,好比把测试经过的数量,没有经过的数量和出错信息打印出来,方便本身观察测试经过状况。

而后须要定义好Json解析器的数据结构,一旦数据结构定义好了,软件就完成了一半。这里咱们会用一个树状结构来组织咱们解析到的数据,每一个数据保存在一个节点里,咱们要作的就是把这个节点定义出来。

根据Json协议,Json一共有7种数据类型:

object, array, string, number, "true", "false", "null"

为了分辨一个节点是哪一种数据类型,咱们须要给节点增长一个type字段,用于标识节点的类型,type的数值咱们能够用一个枚举进行维护。同时为各类数据类型准备一个接收的字段(为了方便处理,没有为true/false/null设置字段)。

type EasyValue struct {
	vType int //节点数据类型
	num   float64
	str   []byte
	len   int
	e     []EasyValue
	o     []EasyObj
}

数据结构搭建好了以后,咱们整个解析器的框架就很清晰了:

  1. 传入一个Json字符串,建立一个根节点,并用解析器进行解析,具体来讲就是逐个字符进行分析。
  2. 假设分析出是一个数字,那么就把这个根节点的数字类型设置为数字,并将解析出来的数字放入到节点的num字段中。
  3. 当咱们想要获取解析的结果时,只须要根据节点的数据类型到节点相应的字段获取对应的值就行了。

以上就是整个Json解析器的思路了,在第一章脑子里奠基了这样的基础后,就有了总体的大局观,后面的章节就是根据各类数据类型进行解析。

2. 解析数字

在解析数字的时候,做者选择了直接调用字符串转数字的库函数,因为库函数的接收域比较宽,有些错误状况须要咱们提早作处理,整体来讲仍是好实现的。

可是在处理的过程当中我却遇到了一个Go语言中比较棘手的问题:在咱们调用字符串转数字的库函数时,是有可能出错的,一般会有两种错误,一个是数字非法(这个字符串不是一个数字),另外一个是数字溢出。在其余语言中都可以很好地判断错误类型,而后向用户端返回相应的错误码。可是Go语言对错误的处理比较简洁,它只提供了一个error接口,接口中只有一个string字段用于说明错误信息。这意味着若是一个函数里同时抛出两个错误,得经过错误信息来判断发生了什么错误。具体来讲就是经过判断一个字符串中是否包含另外一个字符串来分辨错误类型,这彷佛有点土。

f, err := strconv.ParseFloat(convStr, 64)
if err != nil {
	if strings.Contains(err.Error(), strconv.ErrRange.Error()) {
		return EASY_PARSE_NUMBER_TOO_BIG
	}
	return EASY_PARSE_INVALID_VALUE
}

谷歌一番后彷佛仍是没有特别好的解决方案,现有的开源方案和官方给出的方案基本都是对错误进行多一层封装,但这招好像对库函数不太管用。也多是我刚开始用go语言,阅历比较少,在从此的使用中我得留意一下这个问题。

3. 解析字符串 - 4. Unicode

接下来到了解析字符串,在这章被做者的一顿指针操做所折服,可是到了本身实现,发现用Go的Slices彷佛很简单就实现了,就是不知道性能差得大不大。

在这章最大的收获是,入门了Unicode编码。之前编程就是一把梭,编码这些知识扫两眼就跳过去了,出了乱码就谷歌解决方案,没有考虑过背后的知识。但在这里得实打实地处理字符的转换,咱们的目标是把字符串存储为UTF-8的形式,背后的关系得搞清楚。

最先的时候用的是ASCII码,ASCII码只有7位,也就是只能表示128个字符。可是世界上的字符太多了,128远远不够,这个时候就出来了Unicode编码。Unicode编码记录了成千上万个字符,但这也意味着它要更多的存储空间,Unicode的转换形式的缩写就是咱们常见的UTF,而UTF-8就是说把Unicode以8位为一个单元进行存储。

有了这些前置知识以后,咱们就须要对字符串中的Unicode编码进行转换,具体的过程是把Unicode字符转换为对应的码元(十六进制数),而后把十六进制数编码成UTF-8的形式。

按照教程作下来对编码也有了初步的认识,感受良好,这估计就是知识的乐趣吧^_^

5. 解析数组 - 6. 解析对象

到了作解析数组和对象功能时,我感觉到了递归的力量,这可能就是做者称之为递归降低解析器的缘由吧。

可是在这个部分,我最大的收获是深度体会到了单元测试的好处。当解析数组的时候,咱们极可能须要对多个类型的值进行解析,这个时候就把以前单独实现的解析功能给串起来了。

好比说这样一个字符串:

"[123,null,\"abc\",[1,2,3]]"

首先须要解析123,而后解析null,在咱们解析完123的时候,指针应该来到,的位置,经过,进行划分后再继续下一个值的解析。记得当时我在解析单个值的时候没有处理好指针的位置,致使整个数组解析失败了,不过这也加深了我对整个Json字符串解析过程的理解。

至此整个Json解析器的功能已经基本完成,后面两个小节是关于生成器和解析对象访问及其余功能的。

四. 总结

这个教程是用C语言写的,做者用了不少C语言的特性,能很好地提升性能,而我刚入门Go语言,对Go的特性了解甚少,可能在一些地方没有用更适合Go语言的处理方式去处理。

而在咱们平常的开发中,一般会这么用Json:把一个自定义的数据结构转化成Json串,或者是把Json串转换为咱们自定义的结构,目前我尚未实现这样的功能。而对于这样的功能,Go语言给予了原生支持。

我看了一下Go原生解析Json的源码,在解析的思路上和教程是有不少相通之处的。比较大的区别是:在咱们手写的Json解析器中,咱们把解析后的数据存储放咱们自定义的节点结构中。而在Go语言中,因为Json的使用场景经常和结构体相关联,Go语言会把解析出来的数值经过反射直接赋给相应的结构体,这么一来省去了自建数据结构的步骤。

最后很是感谢这个教程,让我对Json的解析有了初步的认识,对测试与重构有了更深的理解,同时也达到了本身的初衷,能熟悉地使用Go语言写分支循环判断了。但我知道Go语言的魅力不在于此,还有不少特性等待着我去学习,继续加油~

相关文章
相关标签/搜索