C语⾔实现Json与结构体相互转换——cson
⽬录
前⾔
之前⽤C语⾔解析过json,虽然借助jansson这样强⼤的解析库,可以将字符串转化为json对象。但是最终我们还是要⼀个字段⼀个字段的取值,当json对象较多,对象嵌套层次有⽐较深时,代码书写起来⾮常繁琐,动辄⼀个解析函数两三百⾏。
最近在做android开发,使⽤json与服务器进⾏数据交换。发现了gson这个好东西(原谅我,刚刚⼲java)——只需定义属性名和json字段名⼀致的java bean,然后调⽤Json或者fromJson就能完成正反序列化。使⽤起来真是爽到极点。gson使⽤了java的反射机制,通过迭代属性列表,直接将json中相应字段的值,赋值给对象的属性。
可不可以把这个思想⽤c语⾔实现呢?
⽰例
先举个例⼦,演⽰⼀下如何使⽤cson解析下⾯这样的⼀个json串。
{
"name":"jay zhou",
"creater":"dahuaxia",
"songList":[
{
"songName":"qilixiang",
"signerName":"jay zhou",
"albumName":"qilixiang"
},
{
"songName":"dongfengpo",
object to"signerName":"jay zhou",
"albumName":"dongfengpo"
}
]
}
⼤致分为以下三步:
1. 定义与Json协议对应的结构体。(即使不⽤cson,⼤概也要这么做)
2. 最最关键的⼀步,定义结构体的属性描述表。(由于C语⾔不像java那样本⾝就⽀持反射,所以我们通过定义属性描述表来描述⼀个结
构体)
3. 调⽤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 o
bject 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字符串;
函数原型:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论