引言
以前可能是去年的去年,写了一个 c json 解析引擎用于一个统计实验数据项目开发中. 基本上能用. 去年在网上
看见了好多开源的c json引擎 .对其中一个比较标准的 cJSON 引擎 深入学习了一下.
以前那个和cJSON对比了一下, 觉得 以前写的那个 优点是 空间小, 速度快, 因为采用的是 用时解析.而cJSON采用
的是递归下降分析, 结构也比较大.最后 决定再重构一个用的cjson. 目前设计思路以通用为主.
其实 结构 就决定 所有. 等同人的性格.
参照资料
1.json 标准 http://www.json.org/
2.cJSON 源码 https://sourceforge.net/projects/cjson/
吐槽一下
网上很多朋友 推荐看cJSON源码, 因为就700多行,可以看看学学. 真的吗 看下面 摘录的代码
1 void cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) { int i = 0;cJSON *c = object->child;while (c && cJSON_strcasecmp(c->string, string))i++, c = c->next;if (c) { newitem->string = cJSON_strdup(string);cJSON_ReplaceItemInArray(object, i, newitem); } }
上面就是cJSON中常出现的代码格式,确实 700多行, 这个700多行全部分开可能要 1400行. 对于这些说700行的朋友只能是呵呵.
但有一点,看了很多nb的json引擎,也就 cJSON最容易理解了.最容易学习了. 性能还行. 这个是不得不说的优点.
后面 再扯一点, 这篇博文也可以理解为cJSON的深入剖析. 最后我采用递归下降分析 语法,构造 cjson_t 结构. 设计比cJSON更好用,更高效.
前言
每次写博文,发现写的好长,不关注的人很难理解, 这次采用一种新思路. 先将通用的简单的C开发技巧 . 后面再讲主题.
1. 积累的C开发 技巧 看完这个 你就已经赚了, 后面就可以不看了
首先我们分享一个 string convert number 的程序, 首先看下面代码.
// 分析数值的子函数,写的可以 double parse_number(const char* str) { double n = 0.0, ns = 1.0, nd = 0.0; //n把偶才能值, ns表示开始正负, 负为-1, nd 表示小数后面位数 int e = 0, es = 1; //e表示后面指数, es表示 指数的正负,负为-1 char c; if ((c = *str) == '-' || c == '+') { ns = c == '-' ? -1.0 : 1.0; //正负号检测, 1表示负数 ++str; } //处理整数部分 for (c = *str; c >= '0' && c <= '9'; c = *++str) n = n * 10 + c - '0'; if (c == '.') for (; (c = *++str) >= '0' && c <= '9'; --nd) n = n * 10 + c - '0'; // 处理科学计数法 if (c == 'e' || c == 'E') { if ((c = *++str) == '+') //处理指数部分 ++str; else if (c == '-') es = -1, ++str; for (; (c = *str) >= '0' && c <= '9'; ++str) e = e * 10 + c - '0'; } //返回最终结果 number = +/- number.fraction * 10^+/- exponent n = ns * n * pow(10.0, nd + es * e); return n; }
推荐有心的人多写几遍, 支持 +19.09 -19.0 19.567e123 -19.09E-9 这些转换. 这是项目工程代码,久经考验.
面试也常考. 没啥意思 , 那还有一个 不错的技巧 如下
/* * 10.1 这里是一个 在 DEBUG 模式下的测试宏 * * 用法 : * DEBUG_CODE({ * puts("debug start..."); * }); */ #ifndef DEBUG_CODE # ifdef _DEBUG # define DEBUG_CODE(code) code # else # define DEBUG_CODE(code) # endif // ! _DEBUG #endif // !DEBUG_CODE
这是个 测试code 宏, 有时候我们想 DEBUG下 测试代码在 Release 模式 删除 . 就用上面宏 .在gcc 下 需要 加上 -I_DEBUG
使用举例如下
/* * 根据索引得到这个数组中对象 * array : 数组对象 * idx : 查找的索引 必须 [0,cjson_getlen(array)) 范围内 * : 返回查找到的当前对象 */ cjson_t cjson_getarray(cjson_t array, int idx) { cjson_t c; DEBUG_CODE({ if (!array || idx < 0) { SL_FATAL("array:%p, idx=%d params is error!", array, idx); return NULL; } }); for (c = array->child; c&&idx > 0; c = c->next) --idx; return c; }
是不是很酷. 到这里 可以认为 值了学到了. 后面不好懂,可看可不看了!
正文
1. json 的语法解析 分析
恭喜到这里了,上面第一个分享的函数还有一种好思路 是 整数部分 和 小数部分分开算,后面再加起来. 就到这里吧.
json 的语法解析 同
从 value 解析开始 遇到的 string number true false null 直接解析
到 array , object 开始解析的时候 解析完毕直接 到value 解析, 这样递归下降解析完成 函数调用的流程图如下:
例如value 解析如下
// 将value 转换塞入 item json值中一部分 static const char* __parse_value(cjson_t item, const char* value) { char c; if ((value) && (c = *value)) { switch (c) { // n = null, f = false, t = true case 'n' : return item->type = _CJSON_NULL, value + 4; case 'f' : return item->type = _CJSON_FALSE, value + 5; case 't' : return item->type = _CJSON_TRUE, item->vd = 1.0, value + 4; case '"': return __parse_string(item, value); case '0' : case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '+' : case '-': return __parse_number(item, value); case '[' : return __parse_array(item, value); case '{' : return __parse_object(item, value); } } // 循环到这里是意外 数据 SL_WARNING("params value = %s!", value); return NULL; }
这里是 value函数入口, 再以 array处理为例 流程 如下
// 分析数组的子函数, 采用递归下降分析 static const char* __parse_array(cjson_t item, const char* str) { cjson_t child; if (*str != '[') { SL_WARNING("array str error start: %s.", str); return NULL; } item->type = _CJSON_ARRAY; str = __skip(str + 1); if (*str == ']') // 低估提前结束 return str + 1; item->child = child = __cjson_new(); str = __skip(__parse_value(child, str)); if (!str) {//解析失败 直接返回 SL_WARNING("array str error e n d one: %s.", str); return NULL; } while (*str == ',') { cjson_t nitem = __cjson_new(); child->next = nitem; nitem->prev = child; child = nitem; str = __skip(__parse_value(child, __skip(str + 1))); if (!str) {// 写代码是一件很爽的事 SL_WARNING("array str error e n d two: %s.", str); return NULL; } } if (*str != ']') { SL_WARNING("array str error e n d: %s.", str); return NULL; } return str + 1; // 跳过']' }
主要看 while中内容,挨个分析 数组中内容,最后又导向于 value中.
大体流程就如上了,通过间接递归处理了 json语法的语句.
2.cjson 的 结构分析
首先看 结构文件
// json 中几种数据类型定义 #define _CJSON_FALSE (0) #define _CJSON_TRUE (1) #define _CJSON_NULL (2) #define _CJSON_NUMBER (3) #define _CJSON_STRING (4) #define _CJSON_ARRAY (5) #define _CJSON_OBJECT (6) #define _CJSON_ISREF (256) //set 时候用如果是引用就不释放了 #define _CJSON_ISCONST (512) //set时候用, 如果是const char* 就不释放了 struct cjson { struct cjson *next, *prev; struct cjson *child; // type == _CJSON_ARRAY or type == _CJSON_OBJECT 那么 child 就不为空 int type; char *key; // json内容那块的 key名称 char *vs; // type == _CJSON_STRING, 是一个字符串 double vd; // type == _CJSON_NUMBER, 是一个num值, ((int)c->vd) 转成int 或 bool }; //定义cjson_t json类型 typedef struct cjson* cjson_t;
来分析一下 struct cjson 中结构字段意思, 其中 next,prev 理解为双向链表, 为了查找同一层的 对象.例如
[1,2,3,4,[6,5]] 其中 1,2,3,4, [6,5] 就是同一层, 6,5 是同一层
4->next 后面 的 child 就是 [6,5]每次解析到新的 array 或 object 都用child 导向它.
type 表示类型 默认有其中 [0,6], 如上 _CJSON_*
key 用于关联对象.
是不是很好理解
这里提供接口如下
/* * 这个宏,协助我们得到 int 值 或 bool 值 * * item : 待处理的目标cjson_t结点 */ #define cjson_getint(item) ((int)((item)->vd)) /* * 删除json串内容 * c : 待释放json_t串内容 */ extern void cjson_delete(cjson_t* pc); /* * 对json字符串解析返回解析后的结果 * jstr : 待解析的字符串 */ extern cjson_t cjson_parse(const char* jstr); /* * 根据 item当前结点的 next 一直寻找到 NULL, 返回个数 *推荐是数组使用 * array : 待处理的cjson_t数组对象 * : 返回这个数组中长度 */ extern int cjson_getlen(cjson_t array); /* * 根据索引得到这个数组中对象 * array : 数组对象 * idx : 查找的索引 必须 [0,cjson_getlen(array)) 范围内 * : 返回查找到的当前对象 */ extern cjson_t cjson_getarray(cjson_t array, int idx); /* * 根据key得到这个对象 相应位置的值 * object : 待处理对象中值 * key : 寻找的key * : 返回 查找 cjson_t 对象 */ extern cjson_t cjson_getobject(cjson_t object, const char* key);
看一遍是不是就理解了,看代码还是比较好懂的. 自己写就难一点了. 主要难在
设计 => 开发 => 测试 => 优化 => 设计 => 开发 => 测试 .................................. 流程很多,出一个好东西最难的是时间和执着.
3.cjson 部分源码分析
先看最重要的 内存释放代码
// 删除cjson static void __cjson_delete(cjson_t c) { cjson_t next; while (c) { next = c->next; //递归删除儿子 if (!(c->type & _CJSON_ISREF)) { if (c->child) //如果不是尾递归,那就先递归 __cjson_delete(c->child); if (c->vs) free(c->vs); } else if (!(c->type & _CJSON_ISCONST) && c->key) free(c->key); free(c); c = next; } } /* * 删除json串内容,最近老是受清华的老学生打击, 会起来的...... * c : 待释放json_t串内容 */ void cjson_delete(cjson_t* pc) { if (!pc || !*pc) return; __cjson_delete(*pc); *pc = NULL; }
上面做法是 防止野指针, 用时间换安全. 时间空间安全 三要素,基本就是编程三大元素. 上面 _CJSOn_ISREF 是为了 set 后面设计留的, 添加了不需要释放的东西
我们就不处理.
在看一个获取关联对象的值
/* * 根据key得到这个对象 相应位置的值 * object : 待处理对象中值 * key : 寻找的key * : 返回 查找 cjson_t 对象 */ cjson_t cjson_getobject(cjson_t object, const char* key) { cjson_t c; DEBUG_CODE({ if (!object || !key || !*key) { SL_FATAL("object:%p, key=%s params is error!", object, key); return NULL; } }); for (c = object->child; c && str_icmp(key, c->key); c = c->next) ; return c; }
是不是很容易 一下都明白了. 其中 str_icmp 上一篇博文中好像讲过源码 如下
/* * 这是个不区分大小写的比较函数 * ls : 左边比较字符串 * rs : 右边比较字符串 * : 返回 ls>rs => >0 ; ls = rs => 0 ; ls<rs => <0 */ int str_icmp(const char* ls, const char* rs) { int l, r; if(!ls || !rs) return (int)ls - (int)rs; do { if((l=*ls++)>='a' && l<='z') l -= 'a' - 'A'; if((r=*rs++)>='a' && r<='z') r -= 'a' - 'A'; } while(l && l==r); return l-r; }
到这里 目前 了解的设计基本就完工了.
4.cjson 源码源码展示
这里就是普通展示所有的源码 首先是 cjson.h
#ifndef _H_CJSON #define _H_CJSON // json 中几种数据类型定义 #define _CJSON_FALSE (0) #define _CJSON_TRUE (1) #define _CJSON_NULL (2) #define _CJSON_NUMBER (3) #define _CJSON_STRING (4) #define _CJSON_ARRAY (5) #define _CJSON_OBJECT (6) #define _CJSON_ISREF (256) //set 时候用如果是引用就不释放了 #define _CJSON_ISCONST (512) //set时候用, 如果是const char* 就不释放了 struct cjson { struct cjson *next, *prev; struct cjson *child; // type == _CJSON_ARRAY or type == _CJSON_OBJECT 那么 child 就不为空 int type; char *key; // json内容那块的 key名称 char *vs; // type == _CJSON_STRING, 是一个字符串 double vd; // type == _CJSON_NUMBER, 是一个num值, ((int)c->vd) 转成int 或 bool }; //定义cjson_t json类型 typedef struct cjson* cjson_t; /* * 这个宏,协助我们得到 int 值 或 bool 值 * * item : 待处理的目标cjson_t结点 */ #define cjson_getint(item) ((int)((item)->vd)) /* * 删除json串内容 * c : 待释放json_t串内容 */ extern void cjson_delete(cjson_t* pc); /* * 对json字符串解析返回解析后的结果 * jstr : 待解析的字符串 */ extern cjson_t cjson_parse(const char* jstr); /* * 根据 item当前结点的 next 一直寻找到 NULL, 返回个数 *推荐是数组使用 * array : 待处理的cjson_t数组对象 * : 返回这个数组中长度 */ extern int cjson_getlen(cjson_t array); /* * 根据索引得到这个数组中对象 * array : 数组对象 * idx : 查找的索引 必须 [0,cjson_getlen(array)) 范围内 * : 返回查找到的当前对象 */ extern cjson_t cjson_getarray(cjson_t array, int idx); /* * 根据key得到这个对象 相应位置的值 * object : 待处理对象中值 * key : 寻找的key * : 返回 查找 cjson_t 对象 */ extern cjson_t cjson_getobject(cjson_t object, const char* key); #endif // !_H_CJSON
后买你是 cjson.c 的实现
#include <cjson.h> #include <schead.h> #include <sclog.h> #include <tstring.h> #include <math.h> // 删除cjson static void __cjson_delete(cjson_t c) { cjson_t next; while (c) { next = c->next; //递归删除儿子 if (!(c->type & _CJSON_ISREF)) { if (c->child) //如果不是尾递归,那就先递归 __cjson_delete(c->child); if (c->vs) free(c->vs); } else if (!(c->type & _CJSON_ISCONST) && c->key) free(c->key); free(c); c = next; } } /* * 删除json串内容,最近老是受清华的老学生打击, 会起来的...... * c : 待释放json_t串内容 */ void cjson_delete(cjson_t* pc) { if (!pc || !*pc) return; __cjson_delete(*pc); *pc = NULL; } //构造一个空 cjson 对象 static inline cjson_t __cjson_new(void) { cjson_t c = calloc(1, sizeof(struct cjson)); if (!c) { SL_FATAL("calloc sizeof struct cjson error!"); exit(_RT_EM); } return c; } // 简化的代码段,用宏来简化代码书写 , 16进制处理 #define __parse_hex4_code(c, h) if (c >= '0' && c <= '9') h += c - '0'; else if (c >= 'A' && c <= 'F') h += 10 + c - 'A'; else if (c >= 'a' && c <= 'z') h += 10 + c - 'F'; else return 0 // 等到unicode char代码 static unsigned __parse_hex4(const char* str) { unsigned h = 0; char c = *str; //第一轮 __parse_hex4_code(c, h); h <<= 4; c = *++str; //第二轮 __parse_hex4_code(c, h); h <<= 4; c = *++str; //第三轮 __parse_hex4_code(c, h); h <<= 4; c = *++str; //第四轮 __parse_hex4_code(c, h); return h; } // 分析字符串的子函数, static const char* __parse_string(cjson_t item, const char* str) { static unsigned char __marks[] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; const char *ptr; char *nptr, *out; int len; char c; unsigned uc, nuc; if (*str != '"') { // 检查是否是字符串内容 SL_WARNING("need \" str => %s error!", str); return NULL; } for (ptr = str + 1, len = 0; (c = *ptr++) != '"' && c; ++len) if (c == '\') //跳过转义字符 ++ptr; if (!(out = malloc(len + 1))) { SL_FATAL("malloc %d size error!", len + 1); return NULL; } // 这里复制拷贝内容 for (ptr = str + 1, nptr = out; (c = *ptr) != '"' && c; ++ptr) { if (c != '\') { *nptr++ = c; continue; } // 处理转义字符 switch ((c = *++ptr)) { case 'b': *nptr++ = ''; break; case 'f': *nptr++ = 'f'; break; case 'n': *nptr++ = ' '; break; case 'r': *nptr++ = ' '; break; case 't': *nptr++ = ' '; break; case 'u': // 将utf16 => utf8, 专门的utf处理代码 uc = __parse_hex4(ptr + 1); ptr += 4;//跳过后面四个字符, unicode if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == 0) break; /* check for invalid. */ if (uc >= 0xD800 && uc <= 0xDBFF) /* UTF16 surrogate pairs. */ { if (ptr[1] != '\' || ptr[2] != 'u') break; /* missing second-half of surrogate. */ nuc = __parse_hex4(ptr + 3); ptr += 6; if (nuc < 0xDC00 || nuc>0xDFFF) break; /* invalid second-half of surrogate. */ uc = 0x10000 + (((uc & 0x3FF) << 10) | (nuc & 0x3FF)); } len = 4; if (uc < 0x80) len = 1; else if (uc < 0x800) len = 2; else if (uc < 0x10000) len = 3; nptr += len; switch (len) { case 4: *--nptr = ((uc | 0x80) & 0xBF); uc >>= 6; case 3: *--nptr = ((uc | 0x80) & 0xBF); uc >>= 6; case 2: *--nptr = ((uc | 0x80) & 0xBF); uc >>= 6; case 1: *--nptr = (uc | __marks[len]); } nptr += len; break; default: *nptr++ = c; } } *nptr = '