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[-] == '\\'))
*in++ = c;
if (c) {
*in++ = c;
++to;
}
continue;
} // step 2 : 处理不可见特殊字符
if (c < '!') {
++to;
continue;
} if (c == '/') {
// step 3 : 处理 // 解析到行末尾
if (to[] == '/') {
while ((c = *++to) && c != '\n')
;
continue;
} // step 4 : 处理 /*
if (to[] == '*') {
while ((c = *++to) && (c != '*' || to[] != '/'))
;
if (c)
to += ;
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 * ) // 屏幕清除宏, 依赖系统脚本
#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 = , //结果正确的返回宏
RT_ErrorBase = -, //错误基类型, 所有错误都可用它, 在不清楚的情况下
RT_ErrorParam = -, //调用的参数错误
RT_ErrorMalloc = -, //内存分配错误
RT_ErrorFopen = -, //文件打开失败
RT_ErrorClose = -, //文件描述符读取关闭, 读取完毕也会返回这个
} 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) < ) \
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), , 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()
#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 = { };
return __u._c == ;
} /**
* 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(, &ots) < ) // 得到当前终端(0表示标准输入)的设置
return EOF; nts = ots;
cfmakeraw(&nts); // 设置终端为Raw原始模式,该模式下所有的输入数据以字节为单位被处理
if (tcsetattr(, TCSANOW, &nts) < ) // 设置上更改之后的设置
return EOF; cr = getchar();
if (tcsetattr(, TCSANOW, &ots) < ) // 设置还原成老的模式
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, , sz);
return ptr;
} // 得到申请内存的开头部分, 并检查
static struct header * _header_get(void * ptr, const char * file, int line, const char * func) {
struct header * node = (struct header *)ptr - ;
// 正常情况直接返回
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 + ;
} // 内部使用的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, , }, * 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 >> ) + ;
unsigned char * ptr = (unsigned char *)str; for (i = h; i >= sp; i -= sp)
h ^= ((h<<) + (h>>) + ptr[i-]); return h ? h : ;
} /*
* 这是个不区分大小写的比较函数
* 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) + );
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 <<= )
;
//分配内存
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 + + ; // c + '\0' 而len只指向 字符串strlen长度 //需要的❀, 需要进行内存分配, 唯一损失
if (len > tstr->size)
_tstr_realloc(tstr, len); tstr->len = --len;
tstr->str[len - ] = 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) + ;
if (len > tstr->size)
_tstr_realloc(tstr, len); strcpy(tstr->str + tstr->len, str);
tstr->len = len - ;
} /*
* 复制tstr中内容,得到char *, 需要自己 free释放
* 假如你要清空tstr_t字符串只需要 设置 len = 0.就可以了
* tstr : 待分配的字符串
* : 返回分配好的字符串首地址
*/
char *
tstr_dupstr(tstr_t tstr) {
char * str;
if (!tstr || tstr->len <= )
return NULL; //下面就可以复制了,采用最快的一种方式
str = sm_malloc(tstr->len + );
return memcpy(str, tstr->str, tstr->len + );
} //------------------------------------------------简单文件辅助操作---------------------------------- /*
* 简单的文件帮助类,会读取完毕这个文件内容返回,失败返回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 >= '' && c <= '') \
h += c - ''; \
else if (c >= 'A' && c <= 'F') \
h += + c - 'A'; \
else if (c >= 'a' && c <= 'z') \
h += + c - 'F'; \
else \
return // 等到unicode char代码
static unsigned _parse_hex4(const char * str) {
unsigned h = ;
char c = *str;
//第一轮
__parse_hex4_code(c, h);
h <<= ;
c = *++str;
//第二轮
__parse_hex4_code(c, h);
h <<= ;
c = *++str;
//第三轮
__parse_hex4_code(c, h);
h <<= ;
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 + , len = ; (c = *ptr++) != '\"' && c; ++len)
if (c == '\\') //跳过转义字符
++ptr;
out = sm_malloc(len + );
// 这里复制拷贝内容
for (ptr = str + , 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 + );
ptr += ;//跳过后面四个字符, unicode
if ((uc >= 0xDC00 && uc <= 0xDFFF) || uc == ) break; /* check for invalid. */ if (uc >= 0xD800 && uc <= 0xDBFF) { /* UTF16 surrogate pairs. */
if (ptr[] != '\\' || ptr[] != 'u')
break; /* missing second-half of surrogate. */
nuc = _parse_hex4(ptr + );
ptr += ;
if (nuc < 0xDC00 || nuc>0xDFFF)
break; /* invalid second-half of surrogate. */
uc = 0x10000 + (((uc & 0x3FF) << ) | (nuc & 0x3FF));
} len = ;
if (uc < 0x80)
len = ;
else if (uc < 0x800)
len = ;
else if (uc < 0x10000)
len = ;
nptr += len; switch (len) {
case : *--nptr = ((uc | 0x80) & 0xBF); uc >>= ;
case : *--nptr = ((uc | 0x80) & 0xBF); uc >>= ;
case : *--nptr = ((uc | 0x80) & 0xBF); uc >>= ;
case : *--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 = , es = ; //e表示后面指数, es表示 指数的正负,负为-1
char c; if ((c = *str) == '-' || c == '+') {
ns = c == '-' ? -1.0 : 1.0; //正负号检测, 1表示负数
++str;
}
//处理整数部分
for (c = *str; c >= '' && c <= ''; c = *++str)
n = n * + c - '';
if (c == '.')
for (; (c = *++str) >= '' && c <= ''; --nd)
n = n * + c - ''; // 处理科学计数法
if (c == 'e' || c == 'E') {
if ((c = *++str) == '+') //处理指数部分
++str;
else if (c == '-')
es = -, ++str;
for (; (c = *str) >= '' && c <= ''; ++str)
e = e * + c - '';
} //返回最终结果 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 + ;
if (*str == ']') // 低估提前结束
return str + ; 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 + );
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 + ; // 跳过']'
} // 分析对象的子函数
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 + ;
if (*str == '}')
return str + ; //处理结点, 开始读取一个 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 + );
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 + );
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+);
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 + ;
} // 将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 + ;
case 'f' : return item->type = _CJSON_FALSE, value + ;
case 't' : return item->type = _CJSON_TRUE, item->vd = 1.0, value + ;
case '\"': return _parse_string(item, value);
case '' : case '': case '': case '': case '': case '': case '': case '': case '': case '':
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[-] == '\\'))
*in++ = c;
if (c) {
*in++ = c;
++to;
}
continue;
} // step 2 : 处理不可见特殊字符
if (c < '!') {
++to;
continue;
} if (c == '/') {
// step 3 : 处理 // 解析到行末尾
if (to[] == '/') {
while ((c = *++to) && c != '\n')
;
continue;
} // step 4 : 处理 /*
if (to[] == '*') {
while ((c = *++to) && (c != '*' || to[] != '/'))
;
if (c)
to += ;
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 = ;
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 < ) {
CERR("array:%p, idx=%d params is error!", array, idx);
return NULL;
}
}); for (c = array->child; c&&idx > ; 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 >> ;
x |= x >> ;
x |= x >> ;
x |= x >> ;
x |= x >> ;
return x + ;
} /*
* 这里使用 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) ? : 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
str = _ensure(p, );
if (str)
str[] = '', str[] = '\0';
}
else if ((fabs(d - i)) <= DBL_EPSILON && d <= INT_MAX && d >= INT_MIN) {
str = _ensure(p, ); //int 值
if (str)
sprintf(str, "%d", i);
}
else {
str = _ensure(p, ); //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 = , flag = ;
unsigned char c; if (!str || !*str) { //最特殊情况,什么都没有 返回NULL
out = _ensure(p, );
if (!out)
return NULL;
out[] = '\"', out[] = '\"', out[] = '\0';
return out;
} for (ptr = str; (c=*ptr); ++ptr)
flag |= ((c > && c < ) || c == '\"' || c == '\\'); if (!flag) { //没有特殊字符直接处理结果
len = (int)(ptr - str);
out = _ensure(p,len + );
if (!out)
return NULL;
nptr = out;
*nptr++ = '\"';
strcpy(nptr, str);
nptr[len] = '\"';
nptr[len + ] = '\0';
return out;
} //处理 存在 "和转义字符内容
for (ptr = str; (c = *ptr) && ++len; ++ptr) {
if (strchr("\"\\\b\f\n\r\t", c))
++len;
else if (c < ) //隐藏字符的处理, 这里可以改
len += ;
} if ((out = _ensure(p, len + )) == NULL)
return NULL;
//先添加 \"
nptr = out;
*nptr++ = '\"';
for (ptr = str; (c = *ptr); ++ptr) {
if (c > && 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 += ; /* 不可见字符 采用 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, ))) strcpy(out, "false"); break;
case _CJSON_TRUE: if ((out = _ensure(p, ))) strcpy(out, "true"); break;
case _CJSON_NULL: if ((out = _ensure(p, ))) 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 = ; (child); child = child->child)
++ncut;
if (!ncut) { //没有孩子结点 直接空数组返回结果
char* out = NULL;
if (!(out = _ensure(p, )))
strcpy(out, "[]");
return out;
} i = p->len;
if (!(ptr = _ensure(p, )))
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, )))
return NULL;
*ptr++ = ',';
*ptr = '\0';
p->len += ;
}
}
if (!(ptr = _ensure(p, )))
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 = ; child; child = child->child)
++ncut;
if (!ncut) {
char* out = NULL;
if (!(out = _ensure(p, )))
strcpy(out, "{}");
return out;
} i = p->len;
if (!(ptr = _ensure(p, )))
return NULL;
*ptr++ = '{';
*ptr -= '\0';
p->len += ;
// 根据子结点 处理
for (child = item->child; (child); child = child->next) {
_print_string(child->key, p);
p->len = _update(p); //加入一个冒号
if (!(ptr = _ensure(p, )))
return NULL;
*ptr++ = ':';
p->len += ; //继续打印一个值
_print_value(child, p);
p->len = _update(p); //结算最后内容
len = child->next ? : ;
if ((ptr = _ensure(p, len + )) == NULL)
return NULL;
if (child->next)
*ptr++ = ',';
*ptr = '\0';
p->len += len;
}
if (!(ptr = _ensure(p, )))
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 = ; 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) + ); // 体积变小 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 <=){
CERR("check param is error! type = %d, len = %d.", type, len);
return NULL;
}
}); // 这里是实际执行代码
a = cjson_newarray();
for(i=; i<len; ++i){
switch(type){
case _CJSON_NULL: n = cjson_newnull(); break;
case _CJSON_FALSE: n = cjson_newbool(array? ((char*)array)[i] : ); break;
case _CJSON_TRUE: n = cjson_newbool(array? ((char*)array)[i] : ); 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<){
CERR("check param is array:%p, idx:%d.", array, idx);
return NULL;
}
}); for(c=array->child; idx> && c; c = c->next)
--idx;
if(c>){
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 = ; 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 = ; 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 / ;
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, )->vs,
cjson_getarray(jsidx, )->vs,
cjson_getint(cjson_getarray(jsidx, )),
cjson_getint(cjson_getarray(jsidx, )),
cjson_getint(cjson_getarray(jsidx, )),
cjson_getint(cjson_getarray(jsidx, )),
cjson_getint(cjson_getarray(jsidx, )),
cjson_getint(cjson_getarray(jsidx, )),
cjson_getint(cjson_getarray(jsidx, )),
cjson_getint(cjson_getarray(jsidx, )),
cjson_getint(cjson_getarray(jsidx, )),
cjson_getint(cjson_getarray(jsidx, )),
cjson_getint(cjson_getarray(jsidx, )),
cjson_getint(cjson_getarray(jsidx, )),
cjson_getint(cjson_getarray(jsidx, )),
cjson_getint(cjson_getarray(jsidx, ))
); 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
c json实战引擎四 , 最后❤跳跃的更多相关文章
- c json实战引擎五 , 优化重构
引言 scjson是一个小巧的纯c跨平台小巧引擎. 适用于替换老的cJSON引擎的场景. 数据结构和代码布局做了大量改进.优势体现在以下几个方面: 1) 跨平台 (window 10 + VS2017 ...
- C json实战引擎 三 , 最后实现部分辅助函数
引言 大学读的是一个很时髦的专业, 学了四年的游戏竞技. 可惜没学好. 但认真过, 比做什么都认真. 见证了 ...... 打的所有游戏人物中 分享一位最喜爱 的 “I've been alone ...
- C json实战引擎 二 , 实现构造部分
引言 这篇博文和前一篇 C json实战引擎一,实现解析部分设计是相同的,都是采用递归下降分析. 这里扯一点 假如你是学生 推荐一本书 给 大家 自制编程语言 http://baike.baidu.c ...
- C json实战引擎 一 , 实现解析部分
引言 以前可能是去年的去年,写了一个 c json 解析引擎用于一个统计实验数据项目开发中. 基本上能用. 去年在网上 看见了好多开源的c json引擎 .对其中一个比较标准的 cJSON 引擎 深入 ...
- c json实战引擎六 , 感觉还行
前言 看到六, 自然有 一二三四五 ... 为什么还要写呢. 可能是它还需要活着 : ) 挣扎升级中 . c json 上面代码也存在于下面项目中(维护的最及时) structc json 这次版本 ...
- MySQL数据库性能优化与监控实战(阶段四)
MySQL数据库性能优化与监控实战(阶段四) 作者 刘畅 时间 2020-10-20 目录 1 sys数据库 1 2 系统变量 1 3 性能优化 1 3.1 硬件层 1 3.2 系统层 1 3.3 软 ...
- Javascript多线程引擎(四)
Javascript多线程引擎(四)--之C语言单继承 因为使用C语言做为开发语言, 而C语言在类的支持方面几乎为零, 而Javascript语言的Object类型是一个非常明显的类支持对象,所以这里 ...
- python机器学习实战(四)
python机器学习实战(三) 版权声明:本文为博主原创文章,转载请指明转载地址 www.cnblogs.com/fydeblog/p/7364317.html 前言 这篇notebook是关于机器学 ...
- apollo客户端springboot实战(四)
1. apollo客户端springboot实战(四) 1.1. 前言 经过前几张入门学习,基本已经完成了apollo环境的搭建和简单客户端例子,但我们现在流行的通常是springboot的客户端 ...
随机推荐
- [USACO06NOV]玉米田Corn Fields 状压DP
题面: 农场主John新买了一块长方形的新牧场,这块牧场被划分成M行N列(1 ≤ M ≤ 12; 1 ≤ N ≤ 12),每一格都是一块正方形的土地.John打算在牧场上的某几格里种上美味的草,供他的 ...
- BZOJ5339:[TJOI2018]教科书般的亵渎——题解
https://www.lydsy.com/JudgeOnline/problem.php?id=5339 https://www.luogu.org/problemnew/show/P4593 小豆 ...
- Mac安装mysqldb
一. 安装mysql (一)下载地址 https://pan.baidu.com/s/1slw50LZ 安装成功后,在系统偏好设置里有MySQL图标,可以启动或关闭MySQL 二. Mysql roo ...
- BZOJ2761 不重复的数字 【treap】
2761: [JLOI2011]不重复数字 Time Limit: 10 Sec Memory Limit: 128 MB Submit: 5517 Solved: 2087 [Submit][S ...
- BZOJ3675 [Apio2014]序列分割 【斜率优化dp】
3675: [Apio2014]序列分割 Time Limit: 40 Sec Memory Limit: 128 MB Submit: 3366 Solved: 1355 [Submit][St ...
- mac, xcode 6.1 安装command line tools 支持,autoconf,automake等
以下软件包 都去我的环境库找到 1 先安装 tcl库 2 安装macports /opt/local/bin/port 一般装到这里 安装autoconf时提示: Warning: The Xcode ...
- linux查找文件目录及mysql卸载
我们要卸载 mysql但是不知道其安装在哪里了,可以用 where +关键词 的方式查找,如上图 输入 whereis mysql 后,下面显示出了4个包含mysql的位置. ..... 查看安装m ...
- 洛谷P3178 [HAOI2015]树上操作(dfs序+线段树)
P3178 [HAOI2015]树上操作 题目链接:https://www.luogu.org/problemnew/show/P3178 题目描述 有一棵点数为 N 的树,以点 1 为根,且树点有边 ...
- JAVA对象的深度克隆
有时候,我们需要把对象A的所有值复制给对象B(B = A),但是这样用等号给赋值你会发现,当B中的某个对象值改变时,同时也会修改到A中相应对象的值! 也许你会说,用clone()不就行了?!你的想法只 ...
- Android LayoutInflater深度解析
1. 题外话 相信大家对LayoutInflate都不陌生,特别在ListView的Adapter的getView方法中基本都会出现,使用inflate方法去加载一个布局,用于ListView的每个I ...