cJSON库使用笔记
JSON:: 轻量级的资料交换语言,该语言以易于让人阅读的文字为基础,用来传输由属性值或者序列性的值组成的数据对象。cJSON是一个轻量级的开源JSON解析器,能够实现使用C语言生成或解析json格式的对象,本文主要记录使用该解析器的过程中,遇到的一些问题。
New
通过了解cJSON库中用于定义cJSON对象的结构体与cJSON_New_Item函数:
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
/* Internal constructor. */
static cJSON *cJSON_New_Item(void)
{
cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON));
if (node) memset(node, 0, sizeof(cJSON));
return node;
}
不难发现,本质上可以将cJSON对象理解为一个双向链表。在新建cJSON对象时,需要对其进行初始化操作,不然会造成存在野指针访问内存的问题。
//实例一个新的cJSON对象
cJSON *json_new = cJSON_CreateObject();
//解析一串符合json格式的字符串,并新建一个cJSON指针指向其解析结果
char *str = "{\"Title\":\"Hello World!\"}";
cJSON *json_parse = cJSON_Parse(str);
Delete
既然cJSON对象的空间是被动态分配出来的,在使用完该对象后自然也需要在内存中释放掉相关资源,cJSON中提供了cJSON_Delete(cJSON *obj);函数便于开发者释放cJSON对象的空间,但是需要注意,在释放空间时只需要释放最上层的父节点,例如以下cJSON对象被新建后:
cJSON *json_root = cJSON_CreateObject();
cJSON *json_array = cJSON_CreateArray();
cJSON *json_item1 = cJSON_CreateObject();
cJSON *json_item2 = cJSON_CreateObject();
cJSON_AddStringToObject(json_item1, "name" , "张三");
cJSON_AddNumberToObject(json_item1, "age" , 15);
cJSON_AddStringToObject(json_item2, "name" , "李四");
cJSON_AddNumberToObject(json_item2, "age" , 20);
cJSON_AddItemToArray(json_array, json_item1);
cJSON_AddItemToArray(json_array, json_item2);
cJSON_AddNumberToObject(json_root, "index" , 0);
cJSON_AddItemToObject(json_root, "list", json_array);
char *str_json = cJSON_Print(json_root);
printf("%s", str_json);
/*
{
"index":1,
"list":[
{
"name":"张三",
"age":15
},
{
"name":"李四",
"age":20
}
]
}
*/
cJSON_Delete(json_root);
free(str_json);
可以看到,在程序的起始部分,新建了四个cJSON对象,但在后续的操作中,json_item1与json_item2先后被添加至json数组json_list中,最后json_list被添加至json_root中,此时根据cJSON对象的结构体成员与cJSON_AddXxxToYyy函数的实现方法,能够看出通过访问json_root->child的子节点便能够遍历其他三个子节点,这时只需要对父节点调用cJSON_Delete()函数即可将子节点一同释放,若在Delete父节点后又对子节点进行了Delete操作,则会引发释放野指针的错误。
上述的对象需要Delete的问题其实有用过cJSON的程序猿应该都知道,接下来这个问题才是让我真正想记录这篇文章的原动力。。。
cJSON *json_root = cJSON_CreateObject();
cJSON_AddNumberToObject(json_root, "index" , 0);
printf(cJSON_Print(json_root));
cJSON_Delete(json_root);
上面这几行代码看着一点毛病也没有,增加、修改、输出、释放一气呵成,实际运行起来也的确成功输出了"{“index”:0}",那么问题点在哪呢?
将cJSON_Print“剥开”,一层一层往里面看,可以发现当需要输出数据类型为整型时,调用了如下函数:
/* Render the number nicely from the given item into a string. */
static char *print_number(cJSON *item, printbuffer *p)
{
char *str = 0;
double d = item->valuedouble;
if (d == 0)
{
if (p) str = ensure(p, 2);
else str = (char*)cJSON_malloc(2); /* special case for 0. */
if (str) strcpy(str, "0");
}
else if (fabs(((double)item->valueint) - d) <= DBL_EPSILON && d <= INT_MAX && d >= INT_MIN)
{
if (p) str = ensure(p, 21);
else str = (char*)cJSON_malloc(21); /* 2^64+1 can be represented in 21 chars. */
if (str) sprintf(str, "%d", item->valueint);
}
else
{
if (p) str = ensure(p, 64);
else str = (char*)cJSON_malloc(64); /* This is a nice tradeoff. */
if (str)
{
if (fabs(floor(d) - d) <= DBL_EPSILON && fabs(d)<1.0e60)sprintf(str, "%.0f", d);
else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9) sprintf(str, "%e", d);
else sprintf(str, "%f", d);
}
}
return str;
}
这也就直接导致了,每打印输出一次,内存中就会对应着产生一段被开辟而不被占用的内存(其实看返回的char*就能够判断出会有这个现象了,本质上还是自己太菜+写代码太不严谨),在电脑上可能用得好好的,但偶然有一次在STM32上用了cJSON,没跑一会直接给我报了个HardFault。。。。急得我把代码拷到VS上看了波内存占用情况,废了半天功夫才找到问题。
Edit
对Json对象中的某部分内容进行修改是一项极为正常的操作,当修改的数据为整型的时候,可能大概看了一遍cJSON的结构体定义,就决定直接使用json_obj->valueint = i的方法来赋值,而但凡看一遍了"cJSON.h",就能够发现在头文件的最底部有这么一个宏定义:
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val))
#define cJSON_SetNumberValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val))
由注释可知,当Json对象中的整型数据被赋值时,需要同时设置下double值。虽然没有遇到过,但是看网上不少老哥踩到了这个坑。而在修改string类型的值时,可以使用cJSON_ReplaceItemInObject函数对对象进行操作。