Skip to content
Published at:

cJSON源码 使用建议及吐嘈

**cJSON repo:**https://github.com/DaveGamble/cJSON

上周在移植cJSON代码的时候遇到了点问题,就顺道跟踪了他的代码,写个文章记录下

Native JSON Benchmark

https://github.com/miloyip/nativejson-benchmark

为什么用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``

c
/* 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_ObjectcJSON_ArraycJSON_StringcJSON_Number
    • json的key用字段string用来存储
    • json的value用字段value开头的成员来存储,比如:
      • valuestring是来存放类型是String的值,
      • valueint和valuedouble用来存放类型是Number的值

那json其它类型Boolean和Null数据存放在哪里呢?答案是放在type里面(cJSON_FalsecJSON_TruecJSON_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,(函数ensurenewsize = 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之类的是否更加合适

Updated at: