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"
        }
    ]
}

大致分为以下三步:

  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);
}

输出结果如下:
C语言实现Json与结构体相互转换——cson_第1张图片
如果不使用cson,直接使用jannson或者cjson的话,就要逐个字段去解析,写起来会很麻烦。
如果字段较多,使用cson的好处就更加明显了。
(脑补一下100个字段,对象又嵌套个5,6层的时候,没有个500行代码估计解析不完)


如果想要了解cson内部实现,建议继续阅读下面内容。

实现

  • 让C语言结构体支持“反射”,即可以通过属性名访问属性值;
  • 序列化:循环访问C语言属性,使用Jansson或者cJSON库为每一个属性构建json对象,最终输出json字符串;
Struct
Jansson/cJSON Object
Json String
序列化
cson
Jansson/cJSON
End
  • 反序列化:使用Jansson或者cJSON库解析json字符串,得到Json对象。循环访问C语言的属性,并在Json对象中查找与属性名相同的json子对象,取值并赋值到结构体属性中;
Json String
Jansson/cJSON Objects
Struct
反序列化
Jansson/cJSON
cson
End

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

你可能感兴趣的:(笔记,json,gson,c语言)