C++的Json库的简单实现

Json

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。 它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。 JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 这些特性使JSON成为理想的数据交换语言。

JSON建构于两种结构:

  • “名称/值”对的集合(A collection of name/value pairs)。不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组 (associative array)。
  • 值的有序列表(An ordered list of values)。在大部分语言中,它被理解为数组(array)。

以上是我在JSON的网站上抄的

实现Json

Json有很多版本,许多比较牛的公司都实现过Json库,但是对于我来说还是个学习的过程,因为写这个项目并不是因为是简单的用自己的方式实现Json,而是通过学习Json库的同时学习一些别的东西,比如写测试单元,调试等,因此最开始从GitHub上找到Milo Yip大神的Json教程,根据教程将Json库用C语言实现了一下,简单的将其实现之后用C/C++将所有代码重写了一下,本文将讲一下我用C++实现时候的思路,C版本的Json是自己之前跟着教程练习的时候写的,就不细细讲了。

我的源码(点这里)和效果图

下面贴一下我的Json实现效果
Json Parse

Json库中的类实现

因为重新用C/C++写的时候希望将接口同已经有的库接口尽量统一,因此写的时候参考的是Jsoncpp的接口,并且类的结构也是参照Jsoncpp实现了Reader、Writer、Value类。

JSON::Value类

Value类是一个提供Json数据存储的一个类,实现增删查改等都是在这个类里面实现,他可以是空(null)、布尔值(true、false)、整型、无符号整型、浮点型、字符串、数组、对象其中的任意一个类型,因此这个类里面我设计成了一个包括所有类型的类,用了一个type标记了Value对象是表示什么类型,其中数组则是使用vector,对象则用map存储。
Value不止承担存储的任务,还承担了增删查改的任务,以及一些其他的任务,因此需要提供一些相关的接口,我实现了append()这种增加对象的接口,removeMember()删除对象的接口,一些类似于map中通过[]来获得和修改Value值的接口,也实现了=修改double、string的接口,以及提供了size()resize()clear()asBool()asString()getMemberNames()等一些功能性接口。
这里可以参考一下Jsoncpp中的接口,贴一下别人博客的链接就不粘贴复制了~~
Jsoncpp的源码地址

JSON::Reader

Reader类相当于教程中的解析类,提供所有类型的解析方法,我这里没有实现intuint的解析,所以数字只能用double存(不骗你我只是懒,其实不难,atoi就好了),这里面没有太复杂的逻辑,需要注意的就是其中数组、对象解析,因为数组和对象的解析需要递归去实现。
在我的简单实现里面用了单例去实现,为什么要用单例,纯粹是因为心血来潮为了实现单例去实现的,其实好像不太需要,不过因为想尝试一下单例,所以就这么去实现了。

JSON::Writer

看了一些资料中写着Writer类是通过纯虚类实现的,并且在写的过程中也确实遇到了需要纯虚类的情况,其实就是由于Value类里面需要提供asString()styledWrite()接口,但是这些接口在Writer里面肯定会去实现,如果重复写的话肯定会代码冗余,但是Writer又需要Value提供数据来转换为字符串,因此需要纯虚类来搞定这个事情。
为什么要写成纯虚类参考这里链接
由于Writer是纯虚类,所以要调用其中定义的函数必定是通过子类,Jsoncpp提供了两个子类FastWriterStyleWriter

FastWriter

FastWriter顾名思义很快,因为他不需要格式化,没有加tab的格式,所以他很快,但是我写的FastWriter我也不知道他快不快,不过Jsoncpp中叫这名字那我就叫这个吧。

StyleWriter

从名字上也可以很清楚知道这个提供的是一个格式化输出方法的类,格式化需要注意的地方也就是对tab的控制,我对tab处理用的是用了四个空格而不是\t,其实我是写博客的时候才想到\t的,影响不大。

实现FastWriter也好,StyleWriter也好,转double、字符串、布尔值都一样,因此这些统一放在基类里面实现,子类只实现三个函数——write()convert_array()convert_object()

接口名称不统一是我一开始觉得自己写的和vector的接口统一用convert_value()一样,然后后面和Jsoncpp统一接口之后之前写的就没改成convertValue()

序列化部分(Reader)

类的结构确定好了,我首先写的是解析部分,Value内当前只提供了存储,不提供其他增删查改的接口,首先把解析实现了。
value

空值、布尔值

空值、布尔值最好处理,由于可能传入的Json对象可能有空格,所以不能简单的用string的operator=处理,用C库的strncmp()很容易就处理了。

number

int和uint用atoi()处理,而double则用strtod()处理,处理前需要先检查一下数字是否合法,因为数字有可能不合法,因此需要我们判断一下,再去利用库函数转换。检查根据下图去检查是否合法,第一个符号,第一个值为0怎么处理,之后小数点处理,指数E和e,E之后的符号,数字判断,合法才去转换,这些都需要我们去做。
number

字符串

字符串需要注意的就是转义字符和UTF-8编码
string

转义字符

字符串需要处理的首先是转义字符,这个比较简单,C语言中读取转义字符是这么判断的:

  1. 首先是\说明是转义字符
  2. 然后读入n表示是\n换行

这个在Json文本中也类似,因为Json文本中也是用\n表示的,只不过我们利用C/C++去处理这个转义文本,在C字符串中是\\n那么我们处理是:

  1. 首先还是读入\,只不过在C字符串中,我们读入的肯定是个\\表示反斜杠
  2. 然后跟了一个n,这个时候我们就在字符串后追加一个\n就好了

其实就是把\n当做两个字符处理,当我们读到\\ + n就表示换行,其余处理类似。

UTF-8转换

当时看懂了,写完就忘了。。。。
大家点击这里看这篇教程中关于UTF-8的编码

array

数组我选择用vector存储,存储过程没有什么比较复杂的,因为里面每一个对象都是一个Value因此,我们要是碰到之前我们写过的对象,只需要调用之前写的方法,然后得到一个解析好的Value,只需要push_back就ok了,注意读取的时候格式就好了。
array

object

对象解析同数组类似,注意格式,key值用解析字符串的方法解析,中间跳过:,后面的值交给对应的解析去解析,之后insert(),然后跳过一个,,也没啥大问题。
object

反序列化输出(Writer部分)

空值、布尔值、string

。。。不写了

number

这里我使用的是sprint输入到数组中,数组足够精度就行,不需要太大,然后用这个数组去创建string返回。

array、object

这里看是否需要格式化,如果不需要格式化,那么只需要在最开始和最后分别加上{}[],中间加上,:即可,比较好控制,中规中矩,中间的数组或对象交给其他去处理就好了。

如果需要格式化,我是使用一个tab_count去控制,每当处理一个数组,tab_count++,处理结束就--,输出可能和标准不一样,我觉得顺眼就OK了。

其他接口

其他接口大部分是在Value类内,Value类内提供一些增加、删除、查找、修改的接口,提供判断接口,这个还是比较简单的,因为用的是C++,可以通过重载operator=就能实现值得修改,如果是对象可以提供一个类似map的接口,重载operator[],返回引用也可以通过=修改。

学习收获

  1. gdb调试比以前熟练了
  2. 内存泄漏检测,摘抄于此,以备复习
	valgrind --leak-check=full  ./leptjson_test
  1. 学会了简单的测试单元的编写
  2. 熟悉了C++的Json库
  3. 大概了解了代码重构
  4. 实际写了一下单例模式

小结

本文是记录一下我学习的过程,以便这之后复习项目用,同时也是为其他学习Json库的同学提供一个思路,我是怎么想的。

发布了89 篇原创文章 · 获赞 96 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/Boring_Wednesday/article/details/86612605