c json实战引擎四 , 最后❤跳跃
引言 - 以前那些系列
长活短说, 写的最终 scjson 纯c跨平台引擎, 希望在合适场景中替代老的csjon引擎, 速度更快, 更轻巧.
下面也是算一个系列吧. 从cjson 中得到灵感, 外加上很久以前自己写的json引擎. 具体的可以看看下面链接.
代码也许有点乱, 但是相信你看 cjson那个源码, 那就更乱了. 有些代码不讲道理, 好吃的东西都得让你难受一下. 才有转折.
本文是为了scjson 手写引擎添加注释解析功能. 再在跨平台上再做一些修改, 最终给出完整的测试demo.
c json实战引擎四 , 最后❤跳跃 (这就是程序语言设计中自举例子)
本文最终的资源 test_scjson.zip
前言 - 那就从json文件看起
先看我们需要处理的 goods.json 文件
[ /* * 物品定义处: * 物品名,品质,作用值,加血,加魔,加攻击,加防御,加速度,加幸运,价格,\ * 占用包裹,加暴击,拥有量,最大拥有,可买(1可买,0不可买),可卖(1可卖, 0不可买) */ ["小灵芝", "低级★", 1, 50, 0, 0, 0, 0, 0, 20, 1, 0, 3, 99, 1, 1], ["中灵芝", "中级★★", 1, 100, 0, 0, 0, 0, 0, 40, 2, 0, 1, 99, 1, 1], ["大灵芝", "高级★★★", 1, 200, 0, 0, 0, 0, 0, 80, 3, 0, 1, 99, 1, 1], ["卤肉 ", "初级★", 1, 80, 0, 0, 0, 0, 0, 30, 1, 0, 0, 99, 1, 0], ["小鸭脖", "初级★", 1, 100, 0, 0, 0, 0, 0, 35, 1, 0, 5, 99, 1, 1], ["小蓝瓶", "初级★", 1, 0, 50, 0, 0, 0, 0, 20, 1, 0, 0, 99, 1, 1], ["中蓝瓶", "中级★★", 1, 0, 100, 0, 0, 0, 0, 40, 2, 0, 0, 99, 1, 1], ["大蓝瓶", "高级★★★", 1, 0, 200, 0, 0, 0, 0, 80, 3, 0, 1, 99, 1, 1], ["金鳌 ", "高级★★★", 3, 0, 0, 5, 0, 0, 0, 200, 2, 0, 2, 99, 1, 1], ["野山椒", "中级★★", 3, 0, 0, 2, 0, 0, 0, 80, 2, 0, 1, 99, 1, 1], ["巨蜥肉", "高级★★★", 3, 0, 0, 0, 10, 0, 0, 100, 3, 0, 1, 99, 1, 1], ["龙血 ", "神级★★★★★", 3, 300, 100, 2, 2, 2, 0, 600, 5, 0, 1, 99, 0, 0], ["龙肉 ", "神级★★★★★", 3, 0, 0, 10, 20, 10, 0, 800, 5, 0, 1, 99, 0, 0], ["木剑 ", "初级★", 2, 0, 0, 10, 0, 0, 0, 50, 1, 0, 1, 1, 0, 0], ["木衣 ", "初级★", 2, 0, 0, 0, 10, 0, 0, 50, 1, 0, 1, 1, 0, 0], ["木鞋 ", "初级★", 2, 0, 0, 0, 0, 10, 0, 50, 1, 0, 1, 1, 0, 0], ["屠龙剑", "神级★★★★★", 2, 0, 0, 100, 0, 0, 0, 10000, 0, 0, 0, 1, 1, 1], ["龙皮铠甲", "神级★★★★★", 2, 0, 0, 0, 200, 0, 0, 10000, 0, 0, 0, 1, 1, 1], ["涅槃丹", "神级★★★★", 3, 100, 100, 100, 100, 20, 10, 5000, 1, 1, 0, 1, 1, 1] ]
扯一点我是用notepad++ 编辑的, 请安装 JsTool 插件处理json很好用
切入正题我们处理思路是, 在文件读取的时候, 去掉无效字符和注释字符. 主要code思路如下
// 从json文件中解析出最简json数据 static tstr_t _cjson_newfile(const char * path) { char c, n; tstr_t tstr; FILE * txt = fopen(path, "r"); if (NULL == txt) { CERR("fopen r %s is error!", path); return NULL; } //这里创建文本串对象 tstr = tstr_new(NULL); while ((c = fgetc(txt)) != EOF) { // step 1 : 处理字符串 if (c == '"') { tstr_append(tstr, c); for (n = c; ((c = fgetc(txt)) != EOF) && (c != '"' || n == '\\'); n = c) tstr_append(tstr, c); if (EOF != c) tstr_append(tstr, c); continue; } // step 2 : 处理不可见特殊字符 if (c < '!') continue; if (c == '/') { // step 3 : 处理 // 解析到行末尾 n = fgetc(txt); if (n == '/') { while ((c = fgetc(txt)) != EOF && c != '\n') ; continue; } // step 4 : 处理 /* if (n == '*') { while ((c = fgetc(txt)) != EOF) { if (c == '*') { n = fgetc(txt); if (n == '/') break; ungetc(n, txt); } } continue; } ungetc(n, txt); } // step 5 : 合法数据直接保存 tstr_append(tstr, c); } fclose(txt);//很重要创建了就要释放,否则会出现隐藏的句柄bug return tstr; }
(请原谅, 这种一言不合就上源码的套路.) 主要分5类
1. "" 字符串, 不处理直接放入
2. 1-32 空字符直接舍弃, ['!' == 33, 可以查看ascii表]
3. // 注释直接 舍弃到 \n, 行尾
4. /* 舍弃到 */ 为止
5. 合法字符直接放入
附注ascii码表如下
挺不错的, 可以自行查查.
同样对于json内存串也是采用相同的处理思路
/* * 将 jstr中 不需要解析的字符串都去掉,并且纪念mini 比男的还平 * jstr : 待处理的json串 * : 返回压缩后的json串长度 */ static int _cjson_mini(char * jstr) { char c, *in = jstr, *to = jstr; while ((c = *to)) { // step 1 : 处理字符串 if (c == '"') { *in++ = c; while ((c = *++to) && (c != '"' || to[-1] == '\\')) *in++ = c; if (c) { *in++ = c; ++to; } continue; } // step 2 : 处理不可见特殊字符 if (c < '!') { ++to; continue; } if (c == '/') { // step 3 : 处理 // 解析到行末尾 if (to[1] == '/') { while ((c = *++to) && c != '\n') ; continue; } // step 4 : 处理 /* if (to[1] == '*') { while ((c = *++to) && (c != '*' || to[1] != '/')) ; if (c) to += 2; continue; } } // step 5 : 合法数据直接保存 *in++ = *to++; } *in = '\0'; return in - jstr; }
到这里我们就为这个json引擎, 添加上了json注释功能了, 下面会搭建测试环境.
正文 - 测试环境搭建
跨平台的 scjson引擎[simple c json] , 跨平台的, 采用 Ubuntu linux 搭建一下. window上更好搞. 首先得到所有文件附在下面
schead.h
#ifndef _H_SIMPLEC_SCHEAD #define _H_SIMPLEC_SCHEAD #include <stdio.h> #include <errno.h> #include <string.h> #include <stdint.h> #include <stddef.h> #include <assert.h> #include <stdbool.h> #include <scalloc.h> /* * error => 以后再说 * 跨平台的丑陋从这里开始 * __GNUC => linux 平台特殊操作 * __MSC_VER => window 平台特殊操作 */ #ifdef __GNUC__ // 下面是依赖GCC编译器实现 #include <unistd.h> #include <sys/time.h> #include <termio.h> // 统一的程序睡眠宏, 单位是毫秒颗粒度 #define SLEEPMS(m) \ usleep(m * 1000) // 屏幕清除宏, 依赖系统脚本 #define CONSOLECLS() \ system("printf '\ec'") /* * 得到用户输入的一个字符 * : 返回得到字符 */ extern int sh_getch(void); #elif _MSC_VER // 下面是依赖Visual Studio编译器实现 #include <Windows.h> #include <direct.h> #include <conio.h> // window 删除目录宏 #define rmdir _rmdir // window 上用_getch 替代了getch, 这里为了让其回来 #define sh_getch _getch #define CONSOLECLS() \ system("cls") #define SLEEPMS(m) \ Sleep(m) #else #error "error : Currently only supports the Visual Studio and GCC!" #endif /* * 错误定义枚举 用于判断返回值状态的状态码 RT_*表示返回标志 * 使用举例 : int flag = scconf_get("pursue"); if(flag < RT_SuccessBase) { sclog_error("get config %s error! flag = %d.", "pursue", flag); exit(EXIT_FAILURE); } * 这里是内部 使用的通用返回值 标志. >=0 表示成功, <0 表示失败的情况 */ typedef enum { RT_SuccessBase = 00, //结果正确的返回宏 RT_ErrorBase = -1, //错误基类型, 所有错误都可用它, 在不清楚的情况下 RT_ErrorParam = -2, //调用的参数错误 RT_ErrorMalloc = -3, //内存分配错误 RT_ErrorFopen = -4, //文件打开失败 RT_ErrorClose = -5, //文件描述符读取关闭, 读取完毕也会返回这个 } flag_e; /* * 定义一些通用的函数指针帮助,主要用于基库的封装中 * 有构造函数, 释放函数, 比较函数等 */ typedef void * (* pnew_f)(); typedef void (* vdel_f)(void * node); // icmp_f 最好 是 int cmp(const void * ln, const void * rn); 标准结构 typedef int (* icmp_f)(); // 循环操作函数, arg 外部参数, node 内部节点 typedef flag_e (* each_f)(void * node, void * arg); /* * c 如果是空白字符返回 true, 否则返回false * c : 必须是 int 值,最好是 char 范围 */ #define sh_isspace(c) \ ((c==' ')||(c>='\t'&&c<='\r')) // 3.0 浮点数据判断宏帮助, __开头表示不希望你使用的宏 #define __DIFF(x, y) ((x)-(y)) //两个表达式做差宏 #define __IF_X(x, z) ((x)<z && (x)>-z) //判断宏,z必须是宏常量 #define EQ(x, y, c) EQ_ZERO(__DIFF(x,y), c) //判断x和y是否在误差范围内相等 // 3.1 float判断定义的宏 #define _FLOAT_ZERO (0.000001f) //float 0的误差判断值 #define EQ_FLOAT_ZERO(x) __IF_X(x, _FLOAT_ZERO) //float 判断x是否为零是返回true #define EQ_FLOAT(x, y) EQ(x, y, _FLOAT_ZERO) //判断表达式x与y是否相等 // 3.2 double判断定义的宏 #define _DOUBLE_ZERO (0.000000000001) //double 0误差判断值 #define EQ_DOUBLE_ZERO(x) __IF_X(x, _DOUBLE_ZERO) //double 判断x是否为零是返回true #define EQ_DOUBLE(x,y) EQ(x, y, _DOUBLE_ZERO) //判断表达式x与y是否相等 // 4.0 控制台打印错误信息, fmt必须是双引号括起来的宏 #ifndef CERR #define CERR(fmt, ...) \ fprintf(stderr,"[%s:%s:%d][error %d:%s]" fmt "\n",\ __FILE__, __func__, __LINE__, errno, strerror(errno), ##__VA_ARGS__) #endif // !CERR // 4.1 控制台打印错误信息并退出, t同样fmt必须是 ""括起来的字符串常量 #ifndef CERR_EXIT #define CERR_EXIT(fmt,...) \ CERR(fmt, ##__VA_ARGS__),exit(EXIT_FAILURE) #endif // !CERR_EXIT // 4.2 执行后检测,如果有错误直接退出 #ifndef IF_CHECK #define IF_CHECK(code) \ if((code) < 0) \ CERR_EXIT(#code) #endif // !IF_CHECK // 5.0 获取数组长度,只能是数组类型或""字符串常量,后者包含'\0' #ifndef LEN #define LEN(arr) \ (sizeof(arr) / sizeof(*(arr))) #endif/* !ARRLEN */ // 7.0 置空操作 #ifndef BZERO // v必须是个变量 #define BZERO(v) \ memset(&(v), 0, sizeof(v)) #endif/* !BZERO */ // 9.0 scanf 健壮的 #ifndef SAFETY_SCANF #define _STR_SAFETY_SCANF "Input error, please according to the prompt!" #define SAFETY_SCANF(scanf_code, ...) \ while(printf(__VA_ARGS__), scanf_code){\ while('\n' != getchar()) \ ;\ puts(_STR_SAFETY_SCANF);\ }\ while('\n' != getchar()) #endif /*!SAFETY_SCANF*/ // 简单的time帮助宏 #ifndef TIME_PRINT #define _STR_TIME_PRINT "The current code block running time:%lf seconds\n" #define TIME_PRINT(code) \ do{\ clock_t __st, __et;\ __st=clock();\ code\ __et=clock();\ printf(_STR_TIME_PRINT, (0.0 + __et - __st) / CLOCKS_PER_SEC);\ } while(0) #endif // !TIME_PRINT /* * 10.0 这里是一个 在 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 //11.0 等待的宏 是个单线程没有加锁 | "请按任意键继续. . ." #define _STR_PAUSEMSG "Press any key to continue . . ." extern void sh_pause(void); #ifndef INIT_PAUSE # ifdef _DEBUG # define INIT_PAUSE() atexit(sh_pause) # else # define INIT_PAUSE() /* 别说了,都重新开始吧 */ # endif #endif // !INIT_PAUSE //12.0 判断是大端序还是小端序,大端序返回true extern bool sh_isbig(void); /** * sh_free - 简单的释放内存函数,对free再封装了一下 **可以避免野指针 **pobj:指向待释放内存的指针(void*) **/ extern void sh_free(void ** pobj); /* * 比较两个结构体栈上内容是否相等,相等返回true,不等返回false * a : 第一个结构体值 * b : 第二个结构体值 * : 相等返回true, 否则false */ #define STRUCTCMP(a, b) \ (!memcmp(&a, &b, sizeof(a))) #endif// ! _H_SIMPLEC_SCHEAD
schead.c
#include <schead.h> //简单通用的等待函数 inline void sh_pause(void) { rewind(stdin); printf(_STR_PAUSEMSG); sh_getch(); } //12.0 判断是大端序还是小端序,大端序返回true inline bool sh_isbig(void) { static union { unsigned short _s; unsigned char _c; } __u = { 1 }; return __u._c == 0; } /** * sh_free - 简单的释放内存函数,对free再封装了一下 **可以避免野指针 **@pobj:指向待释放内存的指针(void*) **/ void sh_free(void ** pobj) { if (pobj == NULL || *pobj == NULL) return; free(*pobj); *pobj = NULL; } // 为linux扩展一些功能 #if defined(__GNUC__) /* * 得到用户输入的一个字符 * : 返回得到字符 */ int sh_getch(void) { int cr; struct termios nts, ots; if (tcgetattr(0, &ots) < 0) // 得到当前终端(0表示标准输入)的设置 return EOF; nts = ots; cfmakeraw(&nts); // 设置终端为Raw原始模式,该模式下所有的输入数据以字节为单位被处理 if (tcsetattr(0, TCSANOW, &nts) < 0) // 设置上更改之后的设置 return EOF; cr = getchar(); if (tcsetattr(0, TCSANOW, &ots) < 0) // 设置还原成老的模式 return EOF; return cr; } #endif
scalloc.h
#ifndef _H_SIMPLEC_SCALLOC #define _H_SIMPLEC_SCALLOC #include <stdlib.h> // 释放sm_malloc_和sm_realloc_申请的内存, 必须配套使用 void sm_free_(void * ptr, const char * file, int line, const char * func); // 返回申请的一段干净的内存 void * sm_malloc_(size_t sz, const char * file, int line, const char * func); // 返回重新申请的内存, 只能和sm_malloc_配套使用 void * sm_realloc_(void * ptr, size_t sz, const char * file, int line, const char * func); /* * 释放申请的内存 * ptr : 申请的内存 */ #define sm_free(ptr) sm_free_(ptr, __FILE__, __LINE__, __func__) /* * 返回申请的内存, 并且是填充'\0' * sz : 申请内存的长度 */ #define sm_malloc(sz) sm_malloc_(sz, __FILE__, __LINE__, __func__) /* * 返回申请到num*sz长度内存, 并且是填充'\0' * num : 申请的数量 * sz : 申请内存的长度 */ #define sm_calloc(num, sz) sm_malloc_(num*sz, __FILE__, __LINE__, __func__) /* * 返回重新申请的内存 * ptr : 申请的内存 * sz : 申请内存的长度 */ #define sm_realloc(ptr, sz) sm_realloc_(ptr, sz, __FILE__, __LINE__, __func__) // 定义全局内存使用宏, 替换原有的malloc系列函数 #ifndef _SIMPLEC_ALLOC_CLOSE # define free sm_free # define malloc sm_malloc # define calloc sm_calloc # define realloc sm_realloc #endif #endif // !_H_SIMPLEC_SCALLOC
scalloc.c
#include <stdio.h> #include <stdlib.h> #include <string.h> // 标识枚举 typedef enum { HF_Alloc, HF_Free } header_e; // 每次申请内存的[16-24]字节额外消耗, 用于记录内存申请情况 struct header { header_e flag; // 当前内存使用的标识 int line; // 申请的文件行 const char * file; // 申请的文件名 const char * func; // 申请的函数名 }; // 内部使用的malloc, 返回内存会用'\0'初始化 void * sm_malloc_(size_t sz, const char * file, int line, const char * func) { struct header * ptr = malloc(sz + sizeof(struct header)); // 检查内存分配的结果 if(NULL == ptr) { fprintf(stderr, "_header_get >%s:%d:%s< alloc error not enough memory start fail!\n", file, line, func); exit(EXIT_FAILURE); } ptr->flag = HF_Alloc; ptr->line = line; ptr->file = file; ptr->func = func; memset(++ptr, 0, sz); return ptr; } // 得到申请内存的开头部分, 并检查 static struct header * _header_get(void * ptr, const char * file, int line, const char * func) { struct header * node = (struct header *)ptr - 1; // 正常情况直接返回 if(HF_Alloc != node->flag) { // 异常情况, 内存多次释放, 和内存无效释放 fprintf(stderr, "_header_get free invalid memony flag %d by >%s:%d:%s<\n", node->flag, file, line, func); exit(EXIT_FAILURE); } return node; } // 内部使用的realloc void * sm_realloc_(void * ptr, size_t sz, const char * file, int line, const char * func) { struct header * node , * buf; if(NULL == ptr) return sm_malloc_(sz, file, line, func); // 合理内存分割 node = _header_get(ptr, file, line, func); node->flag = HF_Free; // 构造返回内存信息 buf = realloc(node, sz + sizeof(struct header)); buf->flag = HF_Alloc; buf->line = line; buf->file = file; buf->func = func; return buf + 1; } // 内部使用的free, 每次释放都会打印日志信息 void sm_free_(void * ptr, const char * file, int line, const char * func) { if(NULL != ptr) { // 得到内存地址, 并且标识一下, 开始释放 struct header * node = _header_get(ptr, file, line, func); node->flag = HF_Free; free(node); } }
tstr.h
#ifndef _H_SIMPLEC_TSTR #define _H_SIMPLEC_TSTR #include <schead.h> //------------------------------------------------简单字符串辅助操作---------------------------------- /* * 主要采用jshash 返回计算后的hash值 * 不冲突率在 80% 左右还可以, 不要传入NULL */ extern unsigned tstr_hash(const char * str); /* * 这是个不区分大小写的比较函数 * ls : 左边比较字符串 * rs : 右边比较字符串 * : 返回 ls>rs => >0 ; ls = rs => 0 ; ls<rs => <0 */ extern int tstr_icmp(const char * ls, const char * rs); /* * 这个代码是 对 strdup 的再实现, 调用之后需要free * str : 待复制的源码内容 * : 返回 复制后的串内容 */ extern char * tstr_dup(const char * str); //------------------------------------------------简单文本字符串辅助操作---------------------------------- #ifndef _STRUCT_TSTR #define _STRUCT_TSTR //简单字符串结构,并定义文本字符串类型tstring struct tstr { char * str; //字符串实际保存的内容 int len; //当前字符串大小 int size; //字符池大小 }; typedef struct tstr * tstr_t; #endif // !_STRUCT_TSTR //文本串栈上创建内容,不想用那些技巧了,就这样吧 #define TSTR_NEW(var) \ struct tstr $__##var = { NULL, 0, 0 }, * var = &$__##var; #define TSTR_DELETE(var) \ sm_free(var->str) /* * tstr_t 的创建函数, 会根据str创建一个 tstr_t 结构的字符串 * str : 待创建的字符串 * : 返回创建好的字符串,如果创建失败返回NULL */ extern tstr_t tstr_new(const char * str); /* * tstr_t 析构函数 * tstr : tstr_t字符串指针量 */ extern void tstr_delete(tstr_t tstr); /* * 向简单文本字符串tstr中添加 一个字符c * tstr : 简单字符串对象 * c : 待添加的字符 */ extern void tstr_append(tstr_t tstr, int c); /* * 向简单文本串中添加只读字符串 * tstr : 文本串 * str : 待添加的素材串 */ extern void tstr_appends(tstr_t tstr, const char * str); /* * 复制tstr中内容,得到char *, 需要自己 free释放 * 假如你要清空tstr_t字符串只需要 设置 len = 0.就可以了 * tstr : 待分配的字符串 * : 返回分配好的字符串首地址 */ extern char * tstr_dupstr(tstr_t tstr); //------------------------------------------------简单文件辅助操作---------------------------------- /* * 简单的文件帮助类,会读取完毕这个文件内容返回,失败返回NULL. * 需要事后使用 tstr_delete(ret); 销毁这个字符串对象 * path : 文件路径 * : 返回创建好的字符串内容,返回NULL表示读取失败 */ extern tstr_t tstr_file_readend(const char * path); /* * 文件写入,没有好说的, 会返回 RT_SuccessBase | RT_ErrorParam | RT_ErrorFopen * path : 文件路径 * str : 待写入的字符串 * : 返回操作的结果 见上面枚举 */ extern int tstr_file_writes(const char * path, const char * str); /* * 文件追加内容 会返回 RT_SuccessBase | RT_ErrorParam | RT_ErrorFopen * path : 文件路径 * str : 待写入的字符串 * : 返回操作的结果 见上面枚举 */ extern int tstr_file_append(const char * path, const char * str); #endif // !_H_SIMPLEC_TSTR
tstr.c
#include <tstr.h> /* * 主要采用jshash 返回计算后的hash值 * 不冲突率在 80% 左右还可以, 不要传入NULL */ unsigned tstr_hash(const char * str) { unsigned i, h = (unsigned)strlen(str), sp = (h >> 5) + 1; unsigned char * ptr = (unsigned char *)str; for (i = h; i >= sp; i -= sp) h ^= ((h<<5) + (h>>2) + ptr[i-1]); return h ? h : 1; } /* * 这是个不区分大小写的比较函数 * ls : 左边比较字符串 * rs : 右边比较字符串 * : 返回 ls>rs => >0 ; ls = rs => 0 ; ls<rs => <0 */ int tstr_icmp(const char * ls, const char * rs) { int l, r; if(!ls || !rs) return (int)(ls - 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; } /* * 这个代码是 对 strdup 的再实现, 调用之后需要free * str : 待复制的源码内容 * : 返回 复制后的串内容 */ char * tstr_dup(const char * str) { size_t len; char * nstr; if (NULL == str) return NULL; len = sizeof(char) * (strlen(str) + 1); nstr = sm_malloc(len); // 返回最后结果 return memcpy(nstr, str, len); } //------------------------------------------------简单文本字符串辅助操作---------------------------------- /* * tstr_t 的创建函数, 会根据str创建一个 tstr_t 结构的字符串 * str : 待创建的字符串 * : 返回创建好的字符串,如果创建失败返回NULL */ tstr_t tstr_new(const char * str) { tstr_t tstr = sm_malloc(sizeof(struct tstr)); tstr_appends(tstr, str); return tstr; } /* * tstr_t 析构函数 * tstr : tstr_t字符串指针量 */ inline void tstr_delete(tstr_t tstr) { if (tstr) { sm_free(tstr->str); sm_free(tstr); } } //文本字符串创建的度量值 #define _INT_TSTRING (32) //简单分配函数,智力一定会分配内存的, len > size的时候调用这个函数 static void _tstr_realloc(tstr_t tstr, int len) { int size = tstr->size; for (size = size < _INT_TSTRING ? _INT_TSTRING : size; size < len; size <<= 1) ; //分配内存 tstr->str = sm_realloc(tstr->str, size); tstr->size = size; } /* * 向简单文本字符串tstr中添加 一个字符c * tstr : 简单字符串对象 * c : 待添加的字符 */ void tstr_append(tstr_t tstr, int c) { //不做安全检查 int len = tstr->len + 1 + 1; // c + '\0' 而len只指向 字符串strlen长度 //需要的❀, 需要进行内存分配, 唯一损失 if (len > tstr->size) _tstr_realloc(tstr, len); tstr->len = --len; tstr->str[len - 1] = c; tstr->str[len] = '\0'; } /* * 向简单文本串中添加只读字符串 * tstr : 文本串 * str : 待添加的素材串 * : 返回状态码主要是 _RT_EP _RT_EM */ void tstr_appends(tstr_t tstr, const char * str) { int len; if (!tstr || !str || !*str) return; // 检查内存是否需要重新构建 len = tstr->len + (int)strlen(str) + 1; if (len > tstr->size) _tstr_realloc(tstr, len); strcpy(tstr->str + tstr->len, str); tstr->len = len - 1; } /* * 复制tstr中内容,得到char *, 需要自己 free释放 * 假如你要清空tstr_t字符串只需要 设置 len = 0.就可以了 * tstr : 待分配的字符串 * : 返回分配好的字符串首地址 */ char * tstr_dupstr(tstr_t tstr) { char * str; if (!tstr || tstr->len <= 0) return NULL; //下面就可以复制了,采用最快的一种方式 str = sm_malloc(tstr->len + 1); return memcpy(str, tstr->str, tstr->len + 1); } //------------------------------------------------简单文件辅助操作---------------------------------- /* * 简单的文件帮助类,会读取完毕这个文件内容返回,失败返回NULL. * 需要事后使用 tstr_delete(ret); 销毁这个字符串对象 * path : 文件路径 * : 返回创建好的字符串内容,返回NULL表示读取失败 */ tstr_t tstr_file_readend(const char * path) { int c; tstr_t tstr; FILE * txt = fopen(path, "r"); if (NULL == txt) { CERR("fopen r %s is error!", path); return NULL; } //这里创建文本串对象 tstr = tstr_new(NULL); //这里读取文本内容 while ((c = fgetc(txt)) != EOF) tstr_append(tstr, c); fclose(txt);//很重要创建了就要释放,否则会出现隐藏的句柄bug return tstr; } int _tstr_file_writes(const char * path, const char * str, const char * mode) { FILE* txt; // 检查参数是否有问题 if (!path || !*path || !str) { CERR("check is '!path || !*path || !str'"); return RT_ErrorParam; } if ((txt = fopen(path, mode)) == NULL) { CERR("fopen mode = '%s', path = '%s' error!", mode, path); return RT_ErrorFopen; } //这里写入信息 fputs(str, txt); fclose(txt); return RT_SuccessBase; } /* * 文件写入,没有好说的, 会返回 RT_SuccessBase | RT_ErrorParam | RT_ErrorFopen * path : 文件路径 * str : 待写入的字符串 * : 返回操作的结果 见上面枚举 */ inline int tstr_file_writes(const char * path, const char * str) { return _tstr_file_writes(path, str, "wb"); } /* * 文件追加内容 会返回 RT_SuccessBase | RT_ErrorParam | RT_ErrorFopen * path : 文件路径 * str : 待写入的字符串 * : 返回操作的结果 见上面枚举 */ inline int tstr_file_appends(const char * path, const char * str) { return _tstr_file_writes(path, str, "ab"); }
scjson.h
#ifndef _H_SIMPLEC_SCJSON #define _H_SIMPLEC_SCJSON #include <tstr.h> // json 中几种数据类型定义 , 对于C而言 最难的是看不见源码,而不是api复杂, 更不是业务复杂 #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 c); /* * 对json字符串解析返回解析后的结果 * jstr : 待解析的字符串 */ extern cjson_t cjson_newtstr(tstr_t str); /* * 将json文件解析成json内容返回. 需要自己调用 cjson_delete * path : json串路径 * : 返回处理好的cjson_t 内容,失败返回NULL */ extern cjson_t cjson_newfile(const char * path); /* * 根据 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); // --------------------------------- 下面是 cjson 输出部分的处理代码 ----------------------------------------- /* * 这里是将 cjson_t item 转换成字符串内容,需要自己free * item : cjson的具体结点 * : 返回生成的item的json串内容 */ extern char* cjson_print(cjson_t item); // --------------------------------- 下面是 cjson 输出部分的辅助代码 ----------------------------------------- /* * 创建一个bool的对象 b==0表示false,否则都是true, 需要自己释放 cjson_delete * b : bool 值 最好是 _Bool * : 返回 创建好的json 内容 */ extern cjson_t cjson_newnull(); extern cjson_t cjson_newbool(int b); extern cjson_t cjson_newnumber(double vd); extern cjson_t cjson_newstring(const char * vs); extern cjson_t cjson_newarray(void); extern cjson_t cjson_newobject(void); /* * 按照类型,创建 对映类型的数组 cjson对象 *目前支持 _CJSON_NULL _CJSON_BOOL/FALSE or TRUE , _CJSON_NUMBER, _CJSON_STRING * NULL => array 传入NULL, FALSE 使用char[],也可以传入NULL, NUMBER 只接受double, string 只接受char** * type : 类型目前支持 上面几种类型 * array : 数组原始数据 * len : 数组中元素长度 * : 返回创建的数组对象 */ extern cjson_t cjson_newtypearray(int type, const void * array, int len); /* * 在array中分离第idx个索引项内容. * array : 待处理的json_t 数组内容 * idx : 索引内容 * : 返回分离的json_t内容 */ extern cjson_t cjson_detacharray(cjson_t array, int idx); /* * 在object json 中分离 key 的项出去 * object : 待分离的对象主体内容 * key : 关联的键 * : 返回分离的 object中 key的项json_t */ extern cjson_t cjson_detachobject(cjson_t object, const char * key); #endif // !_H_SIMPLEC_SCJSON
scjson.c
#include <scjson.h> #include <float.h> #include <limits.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) sm_free(c->vs); } else if (!(c->type & _CJSON_ISCONST) && c->key) sm_free(c->key); sm_free(c); c = next; } } /* * 删除json串内容,最近老是受清华的老学生打击, 会起来的...... * c : 待释放json_t串内容 */ inline void cjson_delete(cjson_t c) { if (NULL == c) return; _cjson_delete(c); } //构造一个空 cjson 对象 static inline cjson_t _cjson_new(void) { return sm_malloc(sizeof(struct cjson)); } // 简化的代码段,用宏来简化代码书写 , 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; char c; int len; unsigned uc, nuc; if (*str != '\"') { // 检查是否是字符串内容 CERR("need \\\" str => %s error!", str); return NULL; } for (ptr = str + 1, len = 0; (c = *ptr++) != '\"' && c; ++len) if (c == '\\') //跳过转义字符 ++ptr; out = sm_malloc(len + 1); // 这里复制拷贝内容 for (ptr = str + 1, nptr = out; (c = *ptr) != '\"' && c; ++ptr) { if (c != '\\') { *nptr++ = c; continue; } // 处理转义字符 switch ((c = *++ptr)) { case 'b': *nptr++ = '\b'; break; case 'f': *nptr++ = '\f'; break; case 'n': *nptr++ = '\n'; break; case 'r': *nptr++ = '\r'; break; case 't': *nptr++ = '\t'; 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 = '\0'; if (c == '\"') ++ptr; item->vs = out; item->type = _CJSON_STRING; return ptr; } // 分析数值的子函数,写的可以 static const char * _parse_number(cjson_t item, 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); item->vd = n; item->type = _CJSON_NUMBER; return str; } // 递归下降分析 需要声明这些函数 static const char * _parse_array(cjson_t item, const char * str); static const char * _parse_object(cjson_t item, const char * str); static const char * _parse_value(cjson_t item, const char * value); // 分析数组的子函数, 采用递归下降分析 static const char * _parse_array(cjson_t item, const char * str) { cjson_t child; if (*str != '[') { CERR("array str error start: %s.", str); return NULL; } item->type = _CJSON_ARRAY; str = str + 1; if (*str == ']') // 低估提前结束 return str + 1; item->child = child = _cjson_new(); str = _parse_value(child, str); if (NULL == str) { // 解析失败 直接返回 CERR("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 = _parse_value(child, str + 1); if (NULL == str) {// 写代码是一件很爽的事 CERR("array str error e n d two: %s.", str); return NULL; } } if (*str != ']') { CERR("array str error e n d: %s.", str); return NULL; } return str + 1; // 跳过']' } // 分析对象的子函数 static const char * _parse_object(cjson_t item, const char * str) { cjson_t child; if (*str != '{') { CERR("object str error start: %s.", str); return NULL; } item->type = _CJSON_OBJECT; str = str + 1; if (*str == '}') return str + 1; //处理结点, 开始读取一个 key item->child = child = _cjson_new(); str = _parse_string(child, str); if (!str || *str != ':') { CERR("_skip _parse_string is error : %s!", str); return NULL; } child->key = child->vs; child->vs = NULL; str = _parse_value(child, str + 1); if (!str) { CERR("_parse_object _parse_value is error 2!"); return NULL; } // 递归解析 while (*str == ',') { cjson_t nitem = _cjson_new(); child->next = nitem; nitem->prev = child; child = nitem; str = _parse_string(child, str + 1); if (!str || *str != ':'){ CERR("_parse_string need name or no equal ':' %s.", str); return NULL; } child->key = child->vs; child->vs = NULL; str = _parse_value(child, str+1); if (!str) { CERR("_parse_string need item two ':' %s.", str); return NULL; } } if (*str != '}') { CERR("object str error e n d: %s.", str); return NULL; } return str + 1; } // 将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); } } // 循环到这里是意外 数据 CERR("params value = %s!", value); return NULL; } /* * 对json字符串解析返回解析后的结果 * jstr : 待解析的字符串 * : 返回解析好的字符串内容 */ static cjson_t _cjson_parse(const char * jstr) { cjson_t c = _cjson_new(); const char * end; if (!(end = _parse_value(c, jstr))) { CERR("_parse_value params end = %s!", end); cjson_delete(c); return NULL; } //这里是否检测 返回测试数据 return c; } /* * 将 jstr中 不需要解析的字符串都去掉,并且纪念mini 比男的还平 * jstr : 待处理的json串 * : 返回压缩后的json串长度 */ static int _cjson_mini(char * jstr) { char c, *in = jstr, *to = jstr; while ((c = *to)) { // step 1 : 处理字符串 if (c == '"') { *in++ = c; while ((c = *++to) && (c != '"' || to[-1] == '\\')) *in++ = c; if (c) { *in++ = c; ++to; } continue; } // step 2 : 处理不可见特殊字符 if (c < '!') { ++to; continue; } if (c == '/') { // step 3 : 处理 // 解析到行末尾 if (to[1] == '/') { while ((c = *++to) && c != '\n') ; continue; } // step 4 : 处理 /* if (to[1] == '*') { while ((c = *++to) && (c != '*' || to[1] != '/')) ; if (c) to += 2; continue; } } // step 5 : 合法数据直接保存 *in++ = *to++; } *in = '\0'; return in - jstr; } /* * 对json字符串解析返回解析后的结果 * jstr : 待解析的字符串 */ inline cjson_t cjson_newtstr(tstr_t str) { str->len = _cjson_mini(str->str); return _cjson_parse(str->str); } // 从json文件中解析出最简json数据 static tstr_t _cjson_newfile(const char * path) { char c, n; tstr_t tstr; FILE * txt = fopen(path, "r"); if (NULL == txt) { CERR("fopen r %s is error!", path); return NULL; } //这里创建文本串对象 tstr = tstr_new(NULL); while ((c = fgetc(txt)) != EOF) { // step 1 : 处理字符串 if (c == '"') { tstr_append(tstr, c); for (n = c; ((c = fgetc(txt)) != EOF) && (c != '"' || n == '\\'); n = c) tstr_append(tstr, c); if (EOF != c) tstr_append(tstr, c); continue; } // step 2 : 处理不可见特殊字符 if (c < '!') continue; if (c == '/') { // step 3 : 处理 // 解析到行末尾 n = fgetc(txt); if (n == '/') { while ((c = fgetc(txt)) != EOF && c != '\n') ; continue; } // step 4 : 处理 /* if (n == '*') { while ((c = fgetc(txt)) != EOF) { if (c == '*') { n = fgetc(txt); if (n == '/') break; ungetc(n, txt); } } continue; } ungetc(n, txt); } // step 5 : 合法数据直接保存 tstr_append(tstr, c); } fclose(txt);//很重要创建了就要释放,否则会出现隐藏的句柄bug return tstr; } /* * 将json文件解析成json内容返回. 需要自己调用 cjson_delete * path : json串路径 * : 返回处理好的cjson_t 内容,失败返回NULL */ cjson_t cjson_newfile(const char * path) { cjson_t root; tstr_t tstr = _cjson_newfile(path); if (!tstr) { CERR("_cjson_dofile_tstr path:%s is error!", path); return NULL; } root = _cjson_parse(tstr->str); tstr_delete(tstr); return root; } /* * 根据 item当前结点的 next 一直寻找到 NULL, 返回个数. 推荐在数组的时候使用 * array : 待处理的cjson_t数组对象 * : 返回这个数组中长度 */ int cjson_getlen(cjson_t array) { int len = 0; if (array) { for (array = array->child; array; array = array->next) ++len; } return len; } /* * 根据索引得到这个数组中对象 * array : 数组对象 * idx : 查找的索引 必须 [0,cjson_getlen(array)) 范围内 * : 返回查找到的当前对象 */ cjson_t cjson_getarray(cjson_t array, int idx) { cjson_t c; DEBUG_CODE({ if (!array || idx < 0) { CERR("array:%p, idx=%d params is error!", array, idx); return NULL; } }); for (c = array->child; c&&idx > 0; c = c->next) --idx; return c; } /* * 根据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) { CERR("object:%p, key=%s params is error!", object, key); return NULL; } }); for (c = object->child; c && tstr_icmp(key, c->key); c = c->next) ; return c; } // --------------------------------- 下面是 cjson 输出部分的处理代码 ----------------------------------------- // 2^n>=x , n是最小的整数 static int _pow2gt(int x) { --x; x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; return x + 1; } /* * 这里使用 tstr_t 结构 size 这里表示 字符串总大小,没有变化 * len 表示当前字符串的字符串起始偏移量 即 tstr_t->str + tstr_t->len 起始的 */ static char* _ensure(tstr_t p, int need) { char * nbuf; int nsize; if (!p || !p->str) { CERR("p:%p need:%d is error!", p, need); return NULL; } need += p->len; if (need <= p->size) //内存够用直接返回结果 return p->str + p->len; nsize = _pow2gt(need); // 一定会成功, 否则一切都回归奇点 nbuf = sm_malloc(nsize * sizeof(char)); //这里复制内容 memcpy(nbuf, p->str, p->size); sm_free(p->str); p->size = nsize; p->str = nbuf; return nbuf + p->len; } // 这里更新一下 当前字符串, 返回当前字符串的长度 static inline int _update(tstr_t p) { return (!p || !p->str) ? 0 : p->len + (int)strlen(p->str+p->len); } // 将item 中值转换成字符串 保存到p中 static char * _print_number(cjson_t item, tstr_t p) { char* str = NULL; double d = item->vd; int i = (int)d; if (d == 0) { //普通0 str = _ensure(p, 2); if (str) str[0] = '0', str[1] = '\0'; } else if ((fabs(d - i)) <= DBL_EPSILON && d <= INT_MAX && d >= INT_MIN) { str = _ensure(p, 21); //int 值 if (str) sprintf(str, "%d", i); } else { str = _ensure(p, 64); //double值 if (str) { double nd = fabs(d); //得到正值开始比较 if(fabs(floor(d) - d) <= DBL_EPSILON && nd < 1.0e60) sprintf(str, "%.0f", d); else if(nd < 1.0e-6 || nd > 1.0e9) //科学计数法 sprintf(str, "%e", d); else sprintf(str, "%f", d); } } return str; } // 输出字符串内容 static char * _print_string(char * str, tstr_t p) { const char * ptr; char * nptr, * out; int len = 0, flag = 0; unsigned char c; if (!str || !*str) { //最特殊情况,什么都没有 返回NULL out = _ensure(p, 3); if (!out) return NULL; out[0] = '\"', out[1] = '\"', out[2] = '\0'; return out; } for (ptr = str; (c=*ptr); ++ptr) flag |= ((c > 0 && c < 32) || c == '\"' || c == '\\'); if (!flag) { //没有特殊字符直接处理结果 len = (int)(ptr - str); out = _ensure(p,len + 3); if (!out) return NULL; nptr = out; *nptr++ = '\"'; strcpy(nptr, str); nptr[len] = '\"'; nptr[len + 1] = '\0'; return out; } //处理 存在 "和转义字符内容 for (ptr = str; (c = *ptr) && ++len; ++ptr) { if (strchr("\"\\\b\f\n\r\t", c)) ++len; else if (c < 32) //隐藏字符的处理, 这里可以改 len += 5; } if ((out = _ensure(p, len + 3)) == NULL) return NULL; //先添加 \" nptr = out; *nptr++ = '\"'; for (ptr = str; (c = *ptr); ++ptr) { if (c > 31 && c != '\"' && c != '\\') { *nptr++ = c; continue; } *nptr++ = '\\'; switch (c){ case '\\': *nptr++ = '\\'; break; case '\"': *nptr++ = '\"'; break; case '\b': *nptr++ = 'b'; break; case '\f': *nptr++ = 'f'; break; case '\n': *nptr++ = 'n'; break; case '\r': *nptr++ = 'r'; break; case '\t': *nptr++ = 't'; break; default: sprintf(nptr, "u%04x", c);nptr += 5; /* 不可见字符 采用 4字节字符编码 */ } } *nptr++ = '\"'; *nptr = '\0'; return out; } //这里是 递归下降 的函数声明处, 分别是处理值, 数组, object static char * _print_value(cjson_t item, tstr_t p); static char * _print_array(cjson_t item, tstr_t p); static char * _print_object(cjson_t item, tstr_t p); // 定义实现部分, 内部私有函数 认为 item 和 p都是存在的 static char * _print_value(cjson_t item, tstr_t p) { char * out = NULL; switch ((item->type) & UCHAR_MAX) { // 0xff case _CJSON_FALSE: if ((out = _ensure(p, 6))) strcpy(out, "false"); break; case _CJSON_TRUE: if ((out = _ensure(p, 5))) strcpy(out, "true"); break; case _CJSON_NULL: if ((out = _ensure(p, 5))) strcpy(out, "null"); break; case _CJSON_NUMBER: out = _print_number(item, p); break; case _CJSON_STRING: out = _print_string(item->vs, p); break; case _CJSON_ARRAY: out = _print_array(item, p); break; case _CJSON_OBJECT: out = _print_object(item, p); break; } return out; } // 同样 假定 item 和 p都是存在且不为NULL static char * _print_array(cjson_t item, tstr_t p) { char * ptr; cjson_t child = item->child; int ncut, i; // 得到孩子结点的深度 for (ncut = 0; (child); child = child->child) ++ncut; if (!ncut) { //没有孩子结点 直接空数组返回结果 char* out = NULL; if (!(out = _ensure(p, 3))) strcpy(out, "[]"); return out; } i = p->len; if (!(ptr = _ensure(p, 1))) return NULL; *ptr = '['; ++p->len; for (child = item->child; (child); child = child->next) { _print_value(child, p); p->len = _update(p); if (child->next) { if (!(ptr = _ensure(p, 2))) return NULL; *ptr++ = ','; *ptr = '\0'; p->len += 1; } } if (!(ptr = _ensure(p, 2))) return NULL; *ptr++ = ']'; *ptr = '\0'; return p->str + i; } // 同样 假定 item 和 p都是存在且不为NULL, 相信这些代码是安全的 static char * _print_object(cjson_t item, tstr_t p) { char * ptr; int i, ncut, len; cjson_t child = item->child; // 得到孩子结点的深度 for (ncut = 0; child; child = child->child) ++ncut; if (!ncut) { char* out = NULL; if (!(out = _ensure(p, 3))) strcpy(out, "{}"); return out; } i = p->len; if (!(ptr = _ensure(p, 2))) return NULL; *ptr++ = '{'; *ptr -= '\0'; p->len += 1; // 根据子结点 处理 for (child = item->child; (child); child = child->next) { _print_string(child->key, p); p->len = _update(p); //加入一个冒号 if (!(ptr = _ensure(p, 1))) return NULL; *ptr++ = ':'; p->len += 1; //继续打印一个值 _print_value(child, p); p->len = _update(p); //结算最后内容 len = child->next ? 1 : 0; if ((ptr = _ensure(p, len + 1)) == NULL) return NULL; if (child->next) *ptr++ = ','; *ptr = '\0'; p->len += len; } if (!(ptr = _ensure(p, 2))) return NULL; *ptr++ = '}'; *ptr = '\0'; return p->str + i; } #define _INT_CJONSTR (256) /* * 这里是将 cjson_t item 转换成字符串内容,需要自己free * item : cjson的具体结点 * : 返回生成的item的json串内容 */ char * cjson_print(cjson_t item) { struct tstr p; char * out; if (NULL == item) { CERR("item is error = %p!", item); return NULL; } // 构建内存 p.str = sm_malloc(sizeof(char) * _INT_CJONSTR); p.size = _INT_CJONSTR; p.len = 0; out = _print_value(item, &p); //从值处理开始, 返回最终结果 if (out == NULL) { sm_free(p.str); CERR("_print_value item:%p, p:%p is error!", item, &p); return NULL; } return sm_realloc(out, strlen(out) + 1); // 体积变小 realloc返回一定成功 } // --------------------------------- 下面是 cjson 输出部分的辅助代码 ----------------------------------------- /* * 创建一个bool的对象 b==0表示false,否则都是true, 需要自己释放 cjson_delete * b : bool 值 最好是 _Bool * : 返回 创建好的json 内容 */ inline cjson_t cjson_newnull() { cjson_t item = _cjson_new(); item->type = _CJSON_NULL; return item; } inline cjson_t cjson_newbool(int b) { cjson_t item = _cjson_new(); item->vd = item->type = b ? _CJSON_TRUE : _CJSON_FALSE; return item; } inline cjson_t cjson_newnumber(double vd) { cjson_t item = _cjson_new(); item->type = _CJSON_NUMBER; item->vd = vd; return item; } inline cjson_t cjson_newstring(const char* vs) { cjson_t item = _cjson_new(); item->type = _CJSON_STRING; item->vs = tstr_dup(vs); return item; } inline cjson_t cjson_newarray(void) { cjson_t item = _cjson_new(); item->type = _CJSON_ARRAY; return item; } inline cjson_t cjson_newobject(void) { cjson_t item = _cjson_new(); item->type = _CJSON_OBJECT; return item; } /* * 按照类型,创建 对映类型的数组 cjson对象 *目前支持 _CJSON_NULL _CJSON_BOOL/FALSE or TRUE , _CJSON_NUMBER, _CJSON_STRING * NULL => array 传入NULL, FALSE 使用char[],也可以传入NULL, NUMBER 只接受double, string 只接受char** * type : 类型目前支持 上面几种类型 * array : 数组原始数据 * len : 数组中元素长度 * : 返回创建的数组对象 */ cjson_t cjson_newtypearray(int type, const void * array, int len) { int i; cjson_t n = NULL, p = NULL, a; // _DEBUG 模式下简单检测一下 DEBUG_CODE({ if(type < _CJSON_FALSE || type > _CJSON_STRING || len <=0){ CERR("check param is error! type = %d, len = %d.", type, len); return NULL; } }); // 这里是实际执行代码 a = cjson_newarray(); for(i=0; i<len; ++i){ switch(type){ case _CJSON_NULL: n = cjson_newnull(); break; case _CJSON_FALSE: n = cjson_newbool(array? ((char*)array)[i] : 0); break; case _CJSON_TRUE: n = cjson_newbool(array? ((char*)array)[i] : 1); break; case _CJSON_NUMBER: n = cjson_newnumber(((double*)array)[i]); break; case _CJSON_STRING: n = cjson_newstring(((char**)array)[i]);break; } if(i){ //有你更好 p->next = n; n->prev = p; } else a->child = n; p = n; } return a; } /* * 在array中分离第idx个索引项内容. * array : 待处理的json_t 数组内容 * idx : 索引内容 * : 返回分离的json_t内容 */ cjson_t cjson_detacharray(cjson_t array, int idx) { cjson_t c; DEBUG_CODE({ if(!array || idx<0){ CERR("check param is array:%p, idx:%d.", array, idx); return NULL; } }); for(c=array->child; idx>0 && c; c = c->next) --idx; if(c>0){ CERR("check param is too dig idx:sub %d.", idx); return NULL; } //这里开始拼接了 if(c->prev) c->prev->next = c->next; if(c->next) c->next->prev = c->prev; if(c == array->child) array->child = c->next; c->prev = c->next = NULL; return c; } /* * 在object json 中分离 key 的项出去 * object : 待分离的对象主体内容 * key : 关联的键 * : 返回分离的 object中 key的项json_t */ cjson_t cjson_detachobject(cjson_t object, const char * key) { cjson_t c; DEBUG_CODE({ if(!object || !object->child || !key || !*key){ CERR("check param is object:%p, key:%s.", object, key); return NULL; } }); for(c=object->child; c && tstr_icmp(c->key, key); c=c->next) ; if(!c) { CERR("check param key:%s => vlaue is empty.", key); return NULL; } if(c->prev) c->prev->next = c->next; if(c->next) c->next->prev = c->prev; if(c == object->child) object->child = c->next; c->prev = c->next = NULL; return c; }
主要测试文件 test_cjson.c
#include <schead.h> #include <scjson.h> // 测试 cjson 函数 void test_readstr(void) { // 第二个 测试 json 串的解析 puts("测试 cjson 是否可用"); char text1[] = "{\n\"name\": \"Jack (\\\"Bee\\\") Nimble\", \n\"format\": {\"type\": \"rect\", \n\"width\": 1920, \n\"height\": 1080, \n\"interlace\": false,\"frame rate\": 24\n}\n}"; TSTR_NEW(jstr1); jstr1->str = text1; cjson_t js = cjson_newtstr(jstr1); cjson_t name = cjson_getobject(js, "name"); printf("name => %s\n", name->vs); cjson_t format = cjson_getobject(js, "format"); printf("len(format) => %d\n", cjson_getlen(format)); cjson_t interlace = cjson_getobject(format, "interlace"); printf("interlace => %d\n", cjson_getint(interlace)); cjson_delete(js); //进行第三组测试 puts(" 测试 数组的读取"); char text2[] = "[\"Sunday\", \"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\"]"; TSTR_NEW(jstr2); jstr2->str = text2; js = cjson_newtstr(jstr2); int len = cjson_getlen(js); int i; for (i = 0; i < len; ++i) { cjson_t item = cjson_getarray(js,i); printf("%d => %s.\n", i, item->vs); } cjson_delete(js); puts("第三组测试"); char text3[] = "[\n [0, -1, 0],\n [1, 0, 0],\n [0, 0, 1]\n ]\n"; TSTR_NEW(jstr3); jstr3->str = text3; js = cjson_newtstr(jstr3); len = cjson_getlen(js); for (i = 0; i < len; ++i) { cjson_t item = cjson_getarray(js, i); printf("%d => %d.\n", i, cjson_getlen(item)); } cjson_delete(js); exit(EXIT_SUCCESS); } /* * simple c 框架业务层启动的代码 */ void test_readfile(void) { // 测试json解析结果是否正常 cjson_t goods = cjson_newfile("./goods.json"); // 数据输出 int len = cjson_getlen(goods); printf("len = %d\n", len); // 打印其中一个数据 int idx = len / 2; cjson_t jsidx = cjson_getarray(goods, idx); int ilen = cjson_getlen(jsidx); printf("ilen = %d\n", ilen); printf("[\"%s\", \"%s\", %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d]\n", cjson_getarray(jsidx, 0)->vs, cjson_getarray(jsidx, 1)->vs, cjson_getint(cjson_getarray(jsidx, 2)), cjson_getint(cjson_getarray(jsidx, 3)), cjson_getint(cjson_getarray(jsidx, 4)), cjson_getint(cjson_getarray(jsidx, 5)), cjson_getint(cjson_getarray(jsidx, 6)), cjson_getint(cjson_getarray(jsidx, 7)), cjson_getint(cjson_getarray(jsidx, 8)), cjson_getint(cjson_getarray(jsidx, 9)), cjson_getint(cjson_getarray(jsidx, 10)), cjson_getint(cjson_getarray(jsidx, 11)), cjson_getint(cjson_getarray(jsidx, 12)), cjson_getint(cjson_getarray(jsidx, 13)), cjson_getint(cjson_getarray(jsidx, 14)), cjson_getint(cjson_getarray(jsidx, 15)) ); cjson_delete(goods); exit(EXIT_SUCCESS); }
最后链接过程, 编译文件 Makefile
CC = gcc DEBUG = -ggdb3 -Wall -D_DEBUG DIR = -I. LIB = -lm RUN = $(CC) $(DEBUG) -o $@ $^ $(LIB) RUNO = $(CC) -c -o $@ $< $(DIR) TEST = -nostartfiles -e $(*F) RUNMAIN = $(RUN) $(TEST) all:test_readstr.out test_readfile.out # 库文件编译 %.o:%.c $(RUNO) test_readstr.out:test_cjson.o scalloc.o tstr.o schead.o scjson.o $(RUNMAIN) test_readfile.out:test_cjson.o scalloc.o tstr.o schead.o scjson.o $(RUNMAIN) # 清除命令 .PHONY:clean clean: rm -rf *.i *.s *.o *.out *~ ; ls
编译结果展示
分别测试解析串和文件结果如下
还有解析goods.json 文件结果的
到这里基本测试完毕了, 这个scjson 引擎也可以收官''截稿'', 欢迎尝试, 也就1200行代码. 很实在, 应该好懂吧. 扯一点对于开发中编码问题, 推荐用UTF-8编码,
对于配置资源, 和操作系统原始编码保持一致.
文末分享一个BUG, 很有意思. 是标准函数是fgetc引起的
_Success_(return != EOF) _Check_return_opt_ _ACRTIMP int __cdecl fgetc( _Inout_ FILE* _Stream );
当你用 int c = fgetc(txt) 的时候, c 总是 >=0 . 走 unsigend char 差不多. 因而造成了逻辑分支出错, 这里需要统一定义为 char c = fgetc(txt);
这个BUG在解析utf-8编码文件会遇到. 是不是很神奇. 切记不要趟坑.
后记 - OK
错误是难免, 欢迎指正, 代码不好读, 说明你没有读过更加意外, 扯蛋的代码.
一个师傅三个徒弟 http://music.163.com/#/song?id=199768