cJSON系列:
上一篇我们已经介绍了cjson结构体 (一)如何学习cJSON,这节就介绍简单的创建json数据并分析源码。
一,创建一个简单的json
例如创建一个如下的json:
{
"years": 22,
"name": "fool",
"man": true,
"adult": false,
"money": null,
"season": ["spring", "summer", "fall","winter"],
"child": {
"girlfriend": "june"
}
}
首先分析这个json,确定它的所有item类型,其中比较需要注意的是child这个item是一个嵌套子json,所以它的类型应该是object,从item的类型入手,先创建所有的item,再把item添加到root节点上。代码如下:
char *str_arry[3] =
{
"spring",
"summer",
"fall",
"winter"
};
//创建不同类型的item
cJSON *root = cJSON_CreateObject();
cJSON *years = cJSON_CreateNumber(22);
cJSON *name = cJSON_CreateString("fool");
cJSON *man = cJSON_CreateTrue();
cJSON *adult = cJSON_CreateFalse();
cJSON *money = cJSON_CreateNull();
cJSON *season = cJSON_CreateStringArray(str_arry, 3);
cJSON *child = cJSON_CreateObject();
cJSON *girlfriend = cJSON_CreateString("june");
//将item添加到父节点下
cJSON_AddItemToObject(root, "years", years);
cJSON_AddItemToObject(root, "name", name);
cJSON_AddItemToObject(root, "man", man);
cJSON_AddItemToObject(root, "adult", adult);
cJSON_AddItemToObject(root, "money", money);
cJSON_AddItemToObject(root, "season", season);
cJSON_AddItemToObject(child, "girlfriend", girlfriend);
cJSON_AddItemToObject(root, "child", child);
//将所有json结构体输出成字符串
char *json=cJSON_Print(root);
printf("%s\n",json);
//! 一定要记得用完擦屁股
cJSON_Delete(root);
这里需要注意的是由于cjson是使用动态内存分配的,当cjson用完后,切记!切记!切记!一定用要用cJSON_Delete释放内存
。
打印结果如下:
二,源码详解
1, CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
//创建一个空的json,赋予obj类型
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void)
{
cJSON *item = cJSON_New_Item(&global_hooks);
if (item)
{
item->type = cJSON_Object;
}
return item;
}
该函数调用的是cJSON_New_Item(),传入参数&global_hooks是全局变量,该变量是有三个函数指针分别指向free(),malloc(),realloc()
,用来操作内存的。
cJSON_New_Item()以动态内存分配的形式,申请了一块内存并将内存初始化。返回指向该内存的指针。
//动态内存分配一个cjson并设为0
static cJSON *cJSON_New_Item(const internal_hooks * const hooks)
{
cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON));
if (node)
{
memset(node, '\0', sizeof(cJSON));
}
return node;
}
2, CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
//创建字符串值为string的json,string值可被cjson_delete删除
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string)
{
//创建空cjson
cJSON *item = cJSON_New_Item(&global_hooks);
if(item)
{
item->type = cJSON_String;
//将string内容复制给valuestring
item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks);
if(!item->valuestring)
{
cJSON_Delete(item);
return NULL;
}
}
return item;
}
cJSON_strdup()也是后续会经常使用的函数,作用是复制字符串。
//开辟一块新内存,并将string复制到内存中,并返回指向内存的指针
static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks)
{
size_t length = 0;
unsigned char *copy = NULL;
if (string == NULL)
{
return NULL;
}
//计算所需内存的长度
length = strlen((const char*)string) + sizeof("");
//申请内存
copy = (unsigned char*)hooks->allocate(length);
if (copy == NULL)
{
return NULL;
}
memcpy(copy, string, length);
return copy;
}
3,CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count);
/*
* 创建字符串型数组的json
* strings:字符串数组
* count:数组长度
* 返回:数组
*/
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char **strings, int count)
{
size_t i = 0;
cJSON *n = NULL; //next
cJSON *p = NULL; //prev
cJSON *a = NULL; //arry
if ((count < 0) || (strings == NULL))
{
return NULL;
}
//创建数组
a = cJSON_CreateArray();
for (i = 0; a && (i < (size_t)count); i++)
{
//创建string类型cjson
n = cJSON_CreateString(strings[i]);
if(!n)
{
cJSON_Delete(a);
return NULL;
}
if(!i)
{
//如果是数组第一个,则将n复制给数组cjson的子节点
a->child = n;
}
else
{
//将n插入p之后(链表操作)
suffix_object(p,n);
}
p = n;
}
return a;
}
4,CJSON_PUBLIC(void) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
cJSON_AddItemToObject()是调用了add_item_to_object()
/*
* 将item以键string添加到obj下
* note:item可以是任何类型的cjson
* constant_key:指示string是否是外部定义的常量数据
* 成功:返回 true
*/
static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key)
{
char *new_key = NULL;
int new_type = cJSON_Invalid;
if ((object == NULL) || (string == NULL) || (item == NULL))
{
return false;
}
//判断key是否是常量数据,若是则不需要重新复制字符串
if (constant_key)
{
//直接取指针,cjson_delete不会删除key
new_key = (char*)cast_away_const(string);
//cjson类型设置为常量字符串
new_type = item->type | cJSON_StringIsConst;
}
else
{
//复制string
new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks);
if (new_key == NULL)
{
return false;
}
//修改cjson类型
new_type = item->type & ~cJSON_StringIsConst;
}
if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
{
//如果string是常量且不为空则释放内存
hooks->deallocate(item->string);
}
//将key赋值给cjson的键
item->string = new_key;
item->type = new_type;
//最后把item添加到obj
return add_item_to_array(object, item);
}
在最后调用了add_item_to_array();
/*
* 添加item到数组最后
* 成功返回 true
*/
static cJSON_bool add_item_to_array(cJSON *array, cJSON *item)
{
cJSON *child = NULL;
if ((item == NULL) || (array == NULL))
{
return false;
}
//child是数组的第一个成员
child = array->child;
//如果数组为空,则item赋予第一个成员child
if (child == NULL)
{
/* list is empty, start new one */
array->child = item;
}
else
{
/* append to the end */
//遍历数组,并在数组最后插入item
while (child->next)
{
child = child->next;
}
suffix_object(child, item);
}
return true;
}
5,CJSON_PUBLIC(void) cJSON_Delete(cJSON *c);
/*
* 将item所在链表的及其子节点的所有cjson释放
*/
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item)
{
cJSON *next = NULL; //指向当前cjson的下一个
while (item != NULL)
{
//由json结构体的定义可知,json结构体自身,string和valuestring是由动态内存分配获得,需要释放这三部分内存
next = item->next;
//如果cjson不是参考外部数据且不为空,则递归调用delete,删除cjson的子节点
if (!(item->type & cJSON_IsReference) && (item->child != NULL))
{
//释放嵌套的json结构体
cJSON_Delete(item->child);
}
//如果cjson不是参考外部数据,则valuestring以及string是由cJSON动态内存分配的,需要释放内存
if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL))
{
global_hooks.deallocate(item->valuestring);
}
if (!(item->type & cJSON_StringIsConst) && (item->string != NULL))
{
global_hooks.deallocate(item->string);
}
//释放当前的cjson
global_hooks.deallocate(item);
//准备释放链表中的下一个cjson
item = next;
}
}
deallocate();可类比free();
这里需要注意的是释放内存时,要判断cjson结构体的string,valuesstring是否是常量。
当string,valuesstring是动态内存分配时,才能使用deallocate()释放,否则可能出现内存错误。具体原因见下一节
三 注意 const,reference
上面的代码中你会发现有些函数中会出现const
,reference
,这是cjson允许使用外部定义的变量来作为cjson结构体的成员。这种情况下的字符串需要用const修饰,结构体用reference修饰。
以cJSON_CreateString()
为例,在为valuestring
赋值时,使用的是动态内存分配的形式申请一块内存,传入参数string复制到内存,在这之后,参数string如何变化对该item的valuestring是没有影响的。
作为对比,以cJSON_CreateStringReference()
为例:
在这个函数中,系统不为valuestring
分配内存,而只是把它指向string
/*
* 创建一个string类型的item,其valuestring指向传入参数string,外部定义的string发生变化会影响item的valuestring
*/
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string)
{
cJSON *item = cJSON_New_Item(&global_hooks);
if (item != NULL)
{
item->type = cJSON_String | cJSON_IsReference;
item->valuestring = (char*)cast_away_const(string);
}
return item;
}
这么做的目的一个方面是可以节约内存,但在使用时需注意参考变量的值不要轻易修改,其他reference类的函数一样,只是参考的变量不同。
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
CJSON_PUBLIC(void) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
CJSON_PUBLIC(void) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(void) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
由此造成:当删除一个item时,需要判断其指针是否是外部定义的变量,以此来决定是否使用内存释放函数。
四 更简单的创建cjson
当你熟悉了cjson对json的创建思路后,可用以下更加快的方式创建cjson:
cJSON_AddNullToObject(root, "money");
cJSON_AddTrueToObject(root, "man");
cJSON_AddFalseToObject(root, "adult");
cJSON_AddNumberToObject(root, "years", 22);
cJSON_AddStringToObject(root, "name", "fool");
cJSON *season = cJSON_CreateStringArray(str_arry, 3);
cJSON_AddItemToObject(root, "season", season);
cJSON *child = cJSON_AddObjectToObject(root, "child");
cJSON_AddStringToObject(child, "girlfriend", "june");
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
可见这个函数本质上是调用了前面提到的cJSON_CreateString();
与add_item_to_object();
就是两步并作一步走。
/*
* 添加string类型cjson到obj
* name :键
* string:值
* 成功返回:string类cjson
*/
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string)
{
cJSON *string_item = cJSON_CreateString(string);
if (add_item_to_object(object, name, string_item, &global_hooks, false))
{
return string_item;
}
cJSON_Delete(string_item);
return NULL;
}
小结
熟悉完json的创建,对json与cjson结构体的对应关系有了更深的掌握,下一章将解析cJSON_Print();输出json。