https://github.com/sunchb/cson.git
之前用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"
}
]
}
大致分为以下三步:
/*
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内部实现,建议继续阅读下面内容。
例如下面这样一个简单的结构体,该如何使用“反射”去访问呢?
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}
};
这一步我们要实现的是,通过给定的属性名和对象,返回属性所在的地址。
函数原型如下:
/**
* @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就可以了。
函数原型如下:
/**
* @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。
通常我们使用的结构体要比这个例子复杂的多。往往会包含其他结构体,或者是基本类型数组,或是结构体类型数组。这样的话我们就需要对上面的reflect_item_t结构进行一些扩充。
增加了三个属性:
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"}
};
有了前面的准备工作,这一步就不算复杂了。
函数原型:
/**
* @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);
和序列化差不多
/**
* @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);
cson