赞
踩
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建构于两种结构:
以上是我在JSON的网站上抄的
Json有很多版本,许多比较牛的公司都实现过Json库,但是对于我来说还是个学习的过程,因为写这个项目并不是因为是简单的用自己的方式实现Json,而是通过学习Json库的同时学习一些别的东西,比如写测试单元,调试等,因此最开始从GitHub上找到Milo Yip大神的Json教程,根据教程将Json库用C语言实现了一下,简单的将其实现之后用C/C++将所有代码重写了一下,本文将讲一下我用C++实现时候的思路,C版本的Json是自己之前跟着教程练习的时候写的,就不细细讲了。
下面贴一下我的Json实现效果
因为重新用C/C++写的时候希望将接口同已经有的库接口尽量统一,因此写的时候参考的是Jsoncpp的接口,并且类的结构也是参照Jsoncpp实现了Reader、Writer、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的源码地址
Reader类相当于教程中的解析类,提供所有类型的解析方法,我这里没有实现int
和uint
的解析,所以数字只能用double存(不骗你我只是懒,其实不难,atoi就好了),这里面没有太复杂的逻辑,需要注意的就是其中数组、对象解析,因为数组和对象的解析需要递归去实现。
在我的简单实现里面用了单例去实现,为什么要用单例,纯粹是因为心血来潮为了实现单例去实现的,其实好像不太需要,不过因为想尝试一下单例,所以就这么去实现了。
看了一些资料中写着Writer类是通过纯虚类实现的,并且在写的过程中也确实遇到了需要纯虚类的情况,其实就是由于Value类里面需要提供asString()
和styledWrite()
接口,但是这些接口在Writer里面肯定会去实现,如果重复写的话肯定会代码冗余,但是Writer又需要Value提供数据来转换为字符串,因此需要纯虚类来搞定这个事情。
为什么要写成纯虚类参考这里链接。
由于Writer是纯虚类,所以要调用其中定义的函数必定是通过子类,Jsoncpp提供了两个子类FastWriter
和StyleWriter
。
FastWriter顾名思义很快,因为他不需要格式化,没有加tab
的格式,所以他很快,但是我写的FastWriter我也不知道他快不快,不过Jsoncpp中叫这名字那我就叫这个吧。
从名字上也可以很清楚知道这个提供的是一个格式化输出方法的类,格式化需要注意的地方也就是对tab的控制,我对tab处理用的是用了四个空格而不是\t
,其实我是写博客的时候才想到\t
的,影响不大。
实现FastWriter也好,StyleWriter也好,转double、字符串、布尔值都一样,因此这些统一放在基类里面实现,子类只实现三个函数——write()
、convert_array()
、convert_object()
。
接口名称不统一是我一开始觉得自己写的和vector的接口统一用convert_value()
一样,然后后面和Jsoncpp统一接口之后之前写的就没改成convertValue()
。
类的结构确定好了,我首先写的是解析部分,Value内当前只提供了存储,不提供其他增删查改的接口,首先把解析实现了。
空值、布尔值最好处理,由于可能传入的Json对象可能有空格,所以不能简单的用string的operator=
处理,用C库的strncmp()
很容易就处理了。
int和uint用atoi()
处理,而double则用strtod()
处理,处理前需要先检查一下数字是否合法,因为数字有可能不合法,因此需要我们判断一下,再去利用库函数转换。检查根据下图去检查是否合法,第一个符号,第一个值为0怎么处理,之后小数点处理,指数E和e,E之后的符号,数字判断,合法才去转换,这些都需要我们去做。
字符串需要注意的就是转义字符和UTF-8编码
字符串需要处理的首先是转义字符,这个比较简单,C语言中读取转义字符是这么判断的:
\
说明是转义字符n
表示是\n
换行这个在Json文本中也类似,因为Json文本中也是用\n
表示的,只不过我们利用C/C++去处理这个转义文本,在C字符串中是\\n
那么我们处理是:
\
,只不过在C字符串中,我们读入的肯定是个\\
表示反斜杠n
,这个时候我们就在字符串后追加一个\n
就好了其实就是把\n
当做两个字符处理,当我们读到\\
+ n
就表示换行,其余处理类似。
当时看懂了,写完就忘了。。。。
大家点击这里看这篇教程中关于UTF-8的编码
数组我选择用vector存储,存储过程没有什么比较复杂的,因为里面每一个对象都是一个Value因此,我们要是碰到之前我们写过的对象,只需要调用之前写的方法,然后得到一个解析好的Value,只需要push_back就ok了,注意读取的时候格式就好了。
对象解析同数组类似,注意格式,key值用解析字符串的方法解析,中间跳过:
,后面的值交给对应的解析去解析,之后insert()
,然后跳过一个,
,也没啥大问题。
。。。不写了
这里我使用的是sprint输入到数组中,数组足够精度就行,不需要太大,然后用这个数组去创建string返回。
这里看是否需要格式化,如果不需要格式化,那么只需要在最开始和最后分别加上{
、}
或[
、]
,中间加上,
或:
即可,比较好控制,中规中矩,中间的数组或对象交给其他去处理就好了。
如果需要格式化,我是使用一个tab_count去控制,每当处理一个数组,tab_count++
,处理结束就--
,输出可能和标准不一样,我觉得顺眼就OK了。
其他接口大部分是在Value类内,Value类内提供一些增加、删除、查找、修改的接口,提供判断接口,这个还是比较简单的,因为用的是C++,可以通过重载operator=
就能实现值得修改,如果是对象可以提供一个类似map的接口,重载operator[]
,返回引用也可以通过=
修改。
valgrind --leak-check=full ./leptjson_test
本文是记录一下我学习的过程,以便这之后复习项目用,同时也是为其他学习Json库的同学提供一个思路,我是怎么想的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。