一. 介绍

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

知乎回答---链接

该回答对应的问题提及了一个开源的“从零开始的JSON库教程”,恰好我刚开始学习go语言,对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语言的魅力不在于此,还有很多特性等待着我去学习,继续加油~

手写Json解析器学习心得的更多相关文章

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

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

  2. Atiit 如何手写词法解析器

    Atiit 如何手写词法解析器 1.1. 通过编程直接从正则->nfa->dfa->表驱动词法解析一条龙自动生成.那是用程序自动生成是需要这样的,自己手写完全不必要这么复杂1 1.2 ...

  3. 手写token解析器、语法解析器、LLVM IR生成器(GO语言)

    最近开始尝试用go写点东西,正好在看LLVM的资料,就写了点相关的内容 - 前端解析器+中间代码生成(本地代码的汇编.执行则靠LLVM工具链完成) https://github.com/daibinh ...

  4. 一起写一个JSON解析器

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

  5. 这个东西,写C++插件的可以用到。 RapidJSON —— C++ 快速 JSON 解析器和生成器

    点这里 原文: RapidJSON —— C++ 快速 JSON 解析器和生成器 时间 2015-04-05 07:33:33  开源中国新闻原文  http://www.oschina.net/p/ ...

  6. 用ExpressionTree实现JSON解析器

    今年的春节与往年不同,对每个人来说都是刻骨铭心的.突入其来的新型冠状病毒使大家过上了“梦想”中的生活:吃了睡,睡了吃,还不用去公司上班,如今这样的生活就在我们面前,可一点都不踏实,只有不停的学习才能让 ...

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

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

  8. mnist手写数字识别——深度学习入门项目(tensorflow+keras+Sequential模型)

    前言 今天记录一下深度学习的另外一个入门项目——<mnist数据集手写数字识别>,这是一个入门必备的学习案例,主要使用了tensorflow下的keras网络结构的Sequential模型 ...

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

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

随机推荐

  1. 看看poll 事件掩码 --- review代码时发现掩码不分的错误

    事件 描述 是否可作为输入(events) 是否可作为输出(revents) POLLIN 数据可读(包括普通数据&优先数据) 是 是 POLLOUT 数据可写(普通数据&优先数据) ...

  2. 《GNU_makefile》第五章——为规则书写命令

    1. 使用make的命令行参数-n或--just-print,make会只显示要执行的命令,不执行,这样方便调试makefile. 2.执行命令 每写一行命令,make会fork出一个shell进程来 ...

  3. menuconfig

    1. menuconfig 的存在意义 原由是 项目的 config 项太多了,需要一个人性化的方式设置. menuconfig 背后是一个应用程序,用户和该应用程序交互,完成 config 设置. ...

  4. com.aliyun.oss.ClientException: Connection error due to: Connection pool shut down

    com.aliyun.oss.ClientException: Connection error due to: Connection pool shut down[ErrorCode]: Unkno ...

  5. 每天一个linux命令之top

    每天一个linux命令之top 转  https://www.linuxprobe.com/chapter-02.html 在图2-6中,top命令执行结果的前5行为系统整体的统计信息,其所代表的含义 ...

  6. win10,ubuntu时间不对问题

    sudo apt-get install ntpdate sudo ntpdate time.windows.com   # ntp2.aliyun.com      然后将时间更新到硬件上: sud ...

  7. 思维导图软件iMindMap制作技巧有哪些

    iMindMap11是iMindMap全新的版本.它可以提供给我们更好的灵活性以便我们将我们的思维进行可视化,并进一步的呈现和开发出属于自己的想法以及思维方式.在iMindMap中我们可以利用思维导图 ...

  8. Mac专用下载器Folx软件中有没有“下载速度控制”功能

    Mac专用下载器Folx软件不仅下载速度快,功能多,而且也可以实现下载上传速度控制的功能.下面小编将在Mac系统平台上,使用Folx 5版本,向大家全面介绍下Folx这款下载软件的速度控制功能,其中包 ...

  9. 教你用Vegas Pro制作视频的遮罩转场特效

    很多小伙伴在接触了Vegas之后,都想利用Vegas制作出各种酷炫的特效.小编也是一样. 今天,小编就和大家分享一下,小编近期学会的遮罩转场特效. 首先想要制作遮罩转场效果,需要的素材有:至少两个图片 ...

  10. MarkDown学习总结-2020.05.11

    1.使用工具 1.1Typora 官网地址:https://www.typora.io/ 下载链接 2.基础入门 注意: []中的内容则是对应格式的标记符,默认全部标识符后面需要多加一个空格才能生效. ...