cJSON源码 使用建议及吐嘈
**cJSON repo:**https://github.com/DaveGamble/cJSON
上周在移植cJSON代码的时候遇到了点问题,就顺道跟踪了他的代码,写个文章记录下
Native JSON Benchmark
https://github.com/miloyip/nativejson-benchmark
![](/assets/Xnip2021-12-28_23-28-44.eIcwFkry.webp)
为什么用cJSON?
从Benchmark上看,cJSON性能上只能算是非常一般的存在;通常选择一个库的理由维度有很多,比如:
- 简单,容易上手
- 方便移植
- 代码量少
- 开发人员已经很熟悉
- 是否经过大项目的检验(比如github上有些库会写,哪些产品引用了我这个库)
作者写cJSON的初衷:
I lifted some JSON from this page: http://www.json.org/fatfree.html That page inspired me to write cJSON, which is a parser that tries to share the same philosophy as JSON itself. Simple, dumb, out of the way.
如何去实现一个JSON解析库?
拆分成两个问题:
- json 怎么解析?
- json 解析后怎么存储?
json 怎么解析?
字符串匹配 + 迭代递归
- 字符串匹配
- 遇到
{
,就当一个json对象类型去解析 - 遇到
[
,就当一个json数组类型去解析 - 遇到
:
,就当一个json键值对去解析 - 遇到值是一个数字开头,就当一个json数字类型去解析
- 遇到值是一个
"
开头,就当一个json字符串类型去解析 - etc
- 遇到
- 迭代递归:继续上面的步骤直到解析完整个json字符串
当然校验json字符串的合法性也是必需的
json 解析后怎么存储?
cjson的答案是:用一个数据结构去描述json的结构,用结构体cJSON去描述json中键值对(key:value);其它(语言的)通用json解析库也是类似的思路,用嵌套的结构体去描述json的结构;原因也很简单,json本来就是源自JavaScript中的Class对象,本身就是JavaScript中的Class
或`Struct``
/* The cJSON structure: */
typedef struct cJSON
{
struct cJSON *next;
struct cJSON *prev;
struct cJSON *child;
int type;
char *valuestring;
int valueint;
double valuedouble;
char *string;
} cJSON;
- next、prev和child是用来维护数据结构
- 其它的用来存放json数据:
- type用来存放json数据类型,比如
cJSON_Object
、cJSON_Array
、cJSON_String
、cJSON_Number
等 - json的key用字段string用来存储
- json的value用字段value开头的成员来存储,比如:
- valuestring是来存放类型是String的值,
- valueint和valuedouble用来存放类型是Number的值
- type用来存放json数据类型,比如
那json其它类型Boolean和Null数据存放在哪里呢?答案是放在type里面(cJSON_False
,cJSON_True
,cJSON_NULL
),估计是为了省内存,没必要为这些json数据类型去增加字段去表示
json解析后数据结构图解:
cJSON源码:
一个框架库里面有一些是核心的函数,其它的是些辅助或是拓展函数;
核心函数围绕三个主题:
- 解析:
cJSON_Parse
内部递归调用parse_value
去解析 - CRUD节点:这部分没有必要多讲
cJSON_CreateXXX
cJSON_AddXXX
cJSON_DetachXXX
cJSON_DeleteXXX
cJSON_ReplaceXXX
- 转成字符串:
cJSON_Print
内部递归调用print_value
去转成字符串;转成字符串中,需要有个buffer来存放字符串,当buffer不够的时候就会增长策略的问题,大约是*2
,(函数ensure
中newsize = needed * 2;
)
其它:
- 上面提到了使用递归,库会对嵌套的层级会有限制
#define CJSON_NESTING_LIMIT 1000
- 对外暴露内存申请了释放的函数:通过
cJSON_InitHooks
函数
cJSON使用建议:
- 删除cJSON结构体中的valueint字段(历史原因保留),int和double数据已经存放在valuedouble上面了
- 复用json中的key字符串,避免为key字符串malloc空间。使用cJSON_AddItemToObjectCS
- cJSON_GetObjectItem和cJSON_GetObjectItemCaseSensitive之间,尽可能用cJSON_GetObjectItemCaseSensitive,cJSON_GetObjectItem内部会在查找key时对其进制toupper转换
- 复用json中的value字符串,使用cJSON_CreateStringReference
- 对于比较长的比较大的json,在转换成json字符串时,建议使用cJSON_PrintBuffered(指定长度)替代cJSON_Print和cJSON_PrintUnformatted,cJSON_Print和cJSON_PrintUnformatted会先分配一个256的buffer,如果不够会从新malloc和拷贝,字符串buffer增长策略基本等于
*2
cJSON吐嘈:
有几个API接口名字不太喜欢
- cJSON_GetArraySize:看上去是获取json数组的大小,其实还能获取json对象的大小
- cJSON_Print:如果有print之类的,可能会想到的是printf,打印到标准输出;cJSON_ToString之类的是否更加合适