C语言实现Json与结构体相互转换——cson
https://github.com/sunchb/cson.git
目录
- 前言
- 示例
- 实现
- 1.实现“反射”
- 1.1.描述结构体属性
- 1.2.访问结构体属性
- 1.3.结构体属性赋值
- 1.3.数组和结构体类型的描述
- 2.序列化
- 3.反序列化
- 代码及Demo
前言
之前用C语言解析过json,虽然借助jansson这样强大的解析库,可以将字符串转化为json对象。但是最终我们还是要一个字段一个字段的取值,当json对象较多,对象嵌套层次有比较深时,代码书写起来非常繁琐,动辄一个解析函数两三百行。
最近在做android开发,使用json与服务器进行数据交换。发现了gson这个好东西(原谅我,刚刚干java)——只需定义属性名和json字段名一致的java bean,然后调用gson.toJson或者fromJson就能完成正反序列化。使用起来真是爽到极点。gson使用了java的反射机制,通过迭代属性列表,直接将json中相应字段的值,赋值给对象的属性。
可不可以把这个思想用c语言实现呢?
示例
先举个例子,演示一下如何使用cson解析下面这样的一个json串。
{"name": "jay zhou","creater": "dahuaxia","songList": [{"songName": "qilixiang","signerName": "jay zhou","albumName": "qilixiang"},{"songName": "dongfengpo","signerName": "jay zhou","albumName": "dongfengpo"}]
}
大致分为以下三步:
- 定义与Json协议对应的结构体。(即使不用cson,大概也要这么做)
- 最最关键的一步,定义结构体的属性描述表。(由于C语言不像java那样本身就支持反射,所以我们通过定义属性描述表来描述一个结构体)
- 调用cson接口完成转换。
/*Step1:定义与json相对应的数据结构
*/
typedef struct {char* songName;char* signerName;char* albumName;
} SongInfo;typedef struct {char* name;char* creater;size_t songNum;SongInfo* songList;
} PlayList;/*Step2:定义数据结构的反射表
*/
reflect_item_t song_ref_tbl[] = {_property_string(SongInfo, songName),_property_string(SongInfo, signerName),_property_string(SongInfo, albumName),_property_end()
};reflect_item_t play_list_ref_tbl[] = {_property_string(PlayList, name),_property_string(PlayList, creater),_property_int_ex(PlayList, songNum, _ex_args_all),_property_array_object(PlayList, songList, song_ref_tbl, SongInfo, songNum), //此处是一个SongInfo类型的结构体数组,所以要关联上SongInfo类型的描述表。_property_end()
};/*Step3:调用csonJsonStr2Struct实现反序列化
*/
void test()
{const static char* jStr = "{\"name\":\"jay zhou\",\"creater\":\"dahuaxia\",\"songList\":[{\"songName\":\"qilixiang\",\"signerName\":\"jay zhou\",\"albumName\":\"qilixiang\"},{\"songName\":\"dongfengpo\",\"signerName\":\"jay zhou\",\"albumName\":\"dongfengpo\"}]}";PlayList playList;memset(&playList, 0, sizeof(playList));/* 将Json解析为结构体,一行代码完成解析 */csonJsonStr2Struct(jStr, &playList, play_list_ref_tbl);/* 测试输出结构体各属性名称和值 */csonPrintProperty(&playList, play_list_ref_tbl);/* 释放为数组和字符串申请的内存 */csonFreePointer(&playList, play_list_ref_tbl);
}
输出结果如下:
如果不使用cson,直接使用jannson或者cjson的话,就要逐个字段去解析,写起来会很麻烦。
如果字段较多,使用cson的好处就更加明显了。
(脑补一下100个字段,对象又嵌套个5,6层的时候,没有个500行代码估计解析不完)
如果想要了解cson内部实现,建议继续阅读下面内容。
实现
- 让C语言结构体支持“反射”,即可以通过属性名访问属性值;
- 序列化:循环访问C语言属性,使用Jansson或者cJSON库为每一个属性构建json对象,最终输出json字符串;
- 反序列化:使用Jansson或者cJSON库解析json字符串,得到Json对象。循环访问C语言的属性,并在Json对象中查找与属性名相同的json子对象,取值并赋值到结构体属性中;
1.实现“反射”
1.1.描述结构体属性
例如下面这样一个简单的结构体,该如何使用“反射”去访问呢?
struct Info{int i;char c;double d;char* str;
};struct Info testInfo = {10000, 1, 0.88, "abc"};
要想实现类似java中反射这样的功能,第一步要做的是定义用于描述结构体属性的结构。
首先我们要知道属性的名字,属性在结构体中的地址,以及大小和类型;
typedef enum {CSON_STRING,CSON_INTEGER,CSON_REAL,
} cson_type;typedef struct reflect_item_t {char* field; /**< field */size_t offset; /**< offset of property */size_t size; /**< size of property */cson_type type; /**< corresponding json type */
}reflect_item_t ;
用上面的结构描述结构体中每一个属性,这样就完成了对结构体的描述。
struct reflect_item_t InfoReflectTbl[] = {/*fiedl offset size type*/{"i", (&(((struct Info*)0)->i)), sizeof(int), CSON_INTEGER},{"c", (&(((struct Info*)0)->c)), sizeof(char), CSON_INTEGER},{"d" (&(((struct Info*)0)->d)), sizeof(double), CSON_REAL},{"str" (&(((struct Info*)0)->str)), sizeof(char*), CSON_REAL}
};
1.2.访问结构体属性
这一步我们要实现的是,通过给定的属性名和对象,返回属性所在的地址。
函数原型如下:
/*** @brief get the address of field by retrieve the property table.** @param obj: object to be operated.* @param field:* @param tbl: property table of the type.** @return address of field.*/
void* csonGetProperty(void* obj, const char* field, const reflect_item_t* tbl);
函数实现也简单,先通过属性名,在属性描述表tbl中找到对应的属性,返回obj + offset就可以了。
1.3.结构体属性赋值
函数原型如下:
/*** @brief set the field of object to specified data.** @param obj: object to be operated.* @param field:* @param data: pointer of specified data.* @param tbl: property table of the type.** @return void.*/
void csonSetProperty(void* obj, const char* field, void* data, const reflect_item_t* tbl);
实现:先通过属性名,在属性描述表tbl中找到对应的属性;然后将data拷贝至obj + offset。
1.3.数组和结构体类型的描述
通常我们使用的结构体要比这个例子复杂的多。往往会包含其他结构体,或者是基本类型数组,或是结构体类型数组。这样的话我们就需要对上面的reflect_item_t结构进行一些扩充。
增加了三个属性:
- reflect_tbl:如果属性为结构体或是结构体数组,需要设置为该类型的属性描述表。
- arrayItemSize:如果属性为数组,需要指定单个数组元素的大小。因为需要为数组动态开辟内存空间。
- arrayCountField:指定用于设定数组大小的字段。(我们解析json数组对象时,我们除了获取数组元素外,还需要保存数组大小。在c语言环境,我们需要数组大小来确保访问不会越界)
typedef struct reflect_item_t {char* field; /**< field */size_t offset; /**< offset of property */size_t size; /**< size of property */cson_type type; /**< corresponding json type */const struct reflect_item_t* reflect_tbl; /**< reflect_tbl of sub-object. must be specified when type is object or object array. */size_t arrayItemSize; /**< size of per array item. must be specified when type is array */char* arrayCountField; /**< field saving array size */
} reflect_item_t;
举个例子:
typedef struct Info{int i;char c;double d;char* str;
}Info;typedef struct ExtData{int infoCount; /* 用于保存数组infos的大小 */Info* infos; /* 结构体数组 */
}ExtData;//描述表Info
struct reflect_item_t ExtDataReflectTbl[] = {/*fiedl offset size type reflect_tbl arrayItemSize arrayCountField*/{"i", (&(((struct Info*)0)->i)), sizeof(int), CSON_INTEGER, NULL, 0, NULL},{"c", (&(((struct Info*)0)->c)), sizeof(char), CSON_INTEGER, NULL, 0, NULL},{"d" (&(((struct Info*)0)->d)), sizeof(double), CSON_REAL, NULL, 0, NULL},{"str" (&(((struct Info*)0)->str)), sizeof(char*), CSON_REAL, NULL, 0, NULL}
};//描述表ExtData
struct reflect_item_t InfoReflectTbl[] = {/*fiedl offset size type reflect_tbl arrayItemSize arrayCountField*/{"infoCount",(&(((ExtData*)0)->infoCount)), sizeof(int), CSON_INTEGER, NULL, 0, NULL},{"infos" (&(((ExtData*)0)->infos)), sizeof(Info), CSON_OBJECT, ExtDataReflectTbl,sizeof(Info), "infoCount"}
};
2.序列化
有了前面的准备工作,这一步就不算复杂了。
- 创建根json对象;(可以使用jansson或是cJSON库)
- 从属性描述表中循环取出每一个属性,再根据类型创建json对象放入根json对象中;
- 将json根对象转为json字符串;
函数原型:
/*** @brief convert struct object to json string.** @param jstr: output json string* @param input: input struct object* @param tbl: property table of input.** @return ERR_NONE on success, otherwise failed.*/
int csonStruct2JsonStr(char** jstr, void* input, const reflect_item_t* tbl);
3.反序列化
和序列化差不多
- 先将json字符串解析为json对象
- 从属性描述表中循环取出每一个属性,再根据类型从json对象中取出对应的字段并赋值;
/*** @brief convert json string to struct object.** @param jstr: json string* @param output: target object* @param tbl: property table of output.** @return ERR_NONE on success, otherwise failed.*/
int csonJsonStr2Struct(const char* jstr, void* output, const reflect_item_t* tbl);
代码及Demo
cson
C语言实现Json与结构体相互转换——cson相关推荐
- Go 语言编程 — 高级数据类型 — 结构体
目录 文章目录 目录 结构体 访问结构体成员 向函数传递结构体 结构体指针 结构体标签(Struct Tag) 结构体 Golang 中,结构体是由一系列具有相同类型或不同类型的数据构成的数据集合.与 ...
- OpenGL ES着色器语言之语句和结构体(官方文档第六章)内建变量(官方文档第七、八章)...
OpenGL ES着色器语言之语句和结构体(官方文档第六章) OpenGL ES着色器语言的程序块基本构成如下: 语句和声明 函数定义 选择(if-else) 迭代(for, while, do-wh ...
- C语言定义了一个结构体怎么分配内存?C\C++中结构体变量与结构体指针内存分配问题?
C语言定义了一个结构体怎么分配内存?C\C++中结构体变量与结构体指针内存分配问题? 问题1:结构体指针最开始怎么分配内存?用sizeof()是不能确定大小的. 问题2:给结构体变量分配之后,是否还要 ...
- c语言学习笔记【结构体02】结构体指针变量与结构体变量的函数参数,C语言学习笔记结构体02结构体指针变量与结构体变量的函数参数.docx...
C 语言学习笔记[结构体02]结构体指针变量与结构体变量 的函数参数 C 语言学习笔记之结构体指针变量一提指针,那可 是 C 语言的核心了,有多少学子曾拜倒在指针的脚下.单纯的说指针,其实并不难,但是 ...
- c语言程序设计实验8,C语言程序设计实验八结构体.doc
C语言程序设计实验八结构体 C语言程序设计实验八:结构体 请求用户输入若干个学生的基本情况,其中包括每位学生的学号.姓名和两门课(C语言.数学)的成绩.然后用下面的格式输出学生的成绩列表. 格式描述如 ...
- c语言如何宏定义枚举型结构体,C语言学习笔记--枚举结构体
枚举 枚举是一种用户定义的数据类型,它用关键字enum以如下语法格式来声明: enum 枚举类型名字 {名字0,名字1,...,名字n}: 枚举类型名字通常并不真的使用,要用的是大括号里面的名字,因为 ...
- C语言的几种结构体Struct的赋值方法
C语言的几种结构体Struct的赋值方法 1. 定义结构体类型 这里是两个结构体的基本结构. struct Init{int a;float b;double c; }I1; struct SIMPL ...
- c语言用typedef定义结构体,C语言结构体定义 typedef struct
c语言规范,定义结构体: typedef struct ANSWER_HEADER { u8 u8Type; u8 u8Code; u32 u32TimeStamp; struct ANSWER_HE ...
- C++入门基础教程(一):C语言的指针与结构体到底怎么用?
目录 一.前言 二.指针 2.1 指针与地址 2.2 指针与函数参数 2.3 指针与数组 2.3.1 指针与一维数组 2.3.2 指针与二维数组 2.4 字符指针与函数 2.5 指针数组以及指向指针的 ...
最新文章
- 静态联编与动态联编之virtual的作用
- icmp反弹shell 简介
- 大数系列之大数相加(ACM通过)(一)
- 30岁,没有月入过万算失败吗?用可视化分析30岁的人收入真相
- Android开发学习笔记:数据存取之File浅析
- 正在使用的文件如何删除?
- 学习C语言的必备书籍-从入门到精通
- c语言数列求和程序137,C语言循环结构
-C语言数列求和(使用while循环)
- 手机怎么设置腾达路由器后显示远端服务器,教你如何用手机快速设置腾达路由器...
- opencv实践中遇到的问题
- php网站整合ck播放器,wordpress整合ckplayer最新版 wp文章短代码调用ck播放器
- fluent周期边界_在Fluent中采用TUI设置周期性边界的方法
- Dungeon Master(地下城主)
- 音视频开发系列-H264编码原理
- java 仓库类_仓库类型和功能分别是什么?
- fastDfs+tracker+nginx在Centos7上配置文件服务器
- 多场景支付融合,刷脸支付入驻新华书店
- mysql Miscellaneous notes
- [Place 30-51] IDELAYCTRL elements have been found to be associated with IODELAY_GROUP ‘CAMERALINK‘,
- Fiddler 如何抓取手机app包以及抓取https 响应