cJSON是一个轻量级且易于扩展的JSON解析开源库。
github地址为:https://github.com/DaveGamble/cJSON
安装与使用
源码安装:将cJSON.c与cJSON.h这两个文件复制入自己的工程项目中,通过#include "cJSON.h"
就可以使用了
编译安装
#克隆cJSON库的源码
git clone https://github.com/DaveGamble/cJSON
cd cJSON
mkdir build
cd build
cmake ..
#编译源码
make
#将头文件放入/usr/local/include/cjson文件夹中,库放入/usr/local/lib,需要超级用户的权限
sudo make install
通过#include
进行使用
数据结构
cJSON使用
cJSON
结构体来存储JSON数据
/* cJSON结构 */
typedef struct cJSON
{
struct cJSON *next;
struct cJSON *prev;
struct cJSON *child;
int type;
char *valuestring;
//不应该直接向valueint写数据,而是使用cJSON_SetNumberValue函数进行赋值
int valueint;
double valuedouble;
char *string;
} cJSON;
cJSON
结构体存储一个JSON的值。cJSON
结构体中的type
是指向JSON值的类型,同时是以bit-flag
的形式存储,这意味着不能仅仅通过比较type
的值来判断JSON值的类型。cJSON_Is...
函数来检查cJSON
结构体存储JSON的值的类型,它会对空指针进行检查,同时返回一个布尔值来判断否是该值。cJSON
可能的值有:
cJSON_Invalid
:无效值,没有存储任何的值。当成员全部清零时便是该值cJSON_False
:为假的布尔值cJSON_True
:为真的布尔值cJSON_NULL
:空值cJOSN_Number
:数值,既作为双精度浮点数存储于valuedouble
,又作为整型存储于valueint
。如果数值超过了整型的范围,valueint
将被赋值为INT_MAX
或者INT_MIN
cJSON_String
:字符串,以’\0’的形式结尾,存储于valuestring
cJSON_Array
:数组,通过cJSON
节点链表来存储array值,每一个元素使用next
和prev
进行相互连接,第一个成员的prev
值为NULL,最后一个成员的next
值为NULL。同时使用成员child
指针来指向该链表cJSON_Object
:对象,与数组有相似的存储方式,唯一不同的是对象会将键值放入成员string
中cJSON_Raw
:JSON格式的字符串,以’\0’结尾,存储于valuestring
。在一次又一次的打印相同的JSON场景下,它能够节省内存。cJSON不会在解析json格式的字符串时产生这种类型。值的注意的是cJSON库不会检查其值是否是合法的cJSON_IsReference
:成员child
指针或者valuestring
指向的节点并不属于自己,自己仅仅是一个引用。因此,cJSON_Delete
和其他相关的函数只会释放这个引用本身,而不会去释放child
或者valuestring
cJSON_StringIsConst
:成员string
指向的是一个字面量,因此,cJSON_Delete
和其他相关的函数不会试图去释放string
的内存使用这些数据结构
对于每一种类型的值都有一个对应的函数
cJSON_Create...
来创建。这类函数会动态分配一个cJSON
的结构,所以需要在使用完后用cJSON_Delete
释放掉内存,以避免内存泄漏。注意:当你已经把一个节点加入了一个数组或者对象,你就不能在用
cJSON_Delete
去释放这个节点的内存了,当该数组或者对象被删除时,这个节点也会被删除
创建基本类型的函数
null
:cJSON_CreateNull
booleans
:cJSON_CreateTrue
,cJSON_CreateFalse
或者cJSON_CreateBool
numbers
:cJSON_CreateNumber
strings
:cJSON_CreateString
数组
你可以使用
cJSON_CreateArray
函数来创建一个新的空数组。cJSON_CreateArrayReference
函数可以创建一个数组的引用,因为它没有属于自己的内容,所以它的子元素不会被cJSON_Delete
给删除
使用
cJSON_AddItemToArray
函数可以在数组的最后增加元素。使用cJSON_AddItemReferenceToArray
函数将会增加一个元素去引用其他的节点,这就意味着cJSON_Delete
不会去删除这个元素的child
或者valuestring
属性,因此当这些属性在其他地方使用的时候,不用担心重复释放内存的事情发生。使用cJSON_InsertItemInArray
函数可以将一个新元素插入数组中的0索引的位置,旧的元素的索引依次加1
如果你想根据索引去移除数组中的一个元素并且继续去使用它,可以使用
cJSON_DetachItemFromArray
函数,它将会返回被分离的数组。为避免内存泄漏,确保将返回值赋值给一个指针
当你需要替换数组中的某一个元素时,
cJSON_ReplaceItemInArray
函数使用索引的方式来进行替换。cJSON_ReplaceItemViaPointer
函数使用指向该元素的指针,同时如果失败则会返回0。这两个函数会分离出旧的元素并删除它,同时在这个位置加入新的元素
使用
cJSON_GetArraySize
函数得到数组的大小。使用cJSON_GetArrayItem
得到一个元素的索引
因为数组是以链表的方式进行存储的,所以通过索引的方式进行遍历效率是很低的( O(n^2) )。建议使用宏
cJSON_ArrayForEach
来遍历数组,它具有时间复杂度为( O(n) )
对象
你可以使用
cJSON_CreateObject
函数来创建一个新的空对象。cJSON_CreateObjectReference
函数可以创建一个对象的引用,因为它没有属于自己的内容,所以它的子元素不会被cJSON_Delete
给删除
使用
cJSON_AddItemToObject
函数来增加一个元素到对象里。使用cJSON_AddItemToObjectCS
函数来增加对象里的元素时,使用的键值(结构体cJSON
中string
成员)是一个引用或者是字面量,因此它会被cJSON_Delete
给忽略。使用cJSON_AddItemReferenceToArray
函数将会增加一个元素去引用其他的节点,这就意味着cJSON_Delete
不会去删除这个元素的child
或者valuestring
属性,因此当这些属性在其他地方使用的时候,不用担心重复释放内存的事情发生
使用
cJSON_DetachItemFromObjectCaseSensitive
函数来从对象中分离出一个元素,从函数命名可以看出对于指定的键值是大小写敏感的,它将会返回被分离的数组。为避免内存泄漏,确保将返回值赋值给一个指针
使用
cJSON_DeleteItemFromObjectCaseSensitive
函数来从一个对象中删除一个元素,可以把它看成先从对象中分离出该元素然后在删除
当你需要替换对象中的某一个元素时,
cJSON_ReplaceItemInObjectCaseSensitive
函数使用j键值查找的方式来进行替换。cJSON_ReplaceItemViaPointer
函数使用指向该元素的指针来查找并替换,同时如果失败则会返回0。这两个函数会分离出旧的元素并删除它,同时在这个位置加入新的元素
因为对象的存储方式和数组很像,所以同样可以通过
cJSON_GetArraySize
来得到对象里元素的个数
使用
cJSON_GetObjectItemCaseSensitive
来访问对象中的某一个元素
使用宏
cJSON_ArrayForEach
来遍历一个对象
cJSON
同样也提供便利的工具函数来快速的在对象内部创建一个新的元素,比如说cJSON_AddNullToObject
函数将会返回新加的元素指针,如果失败则返回NULL
解析JSON字符串
可以使用
cJSON_Parse
函数来一些以’\0’结尾的字符串进行解析
cJSON *json = cJSON_Parse(string);
解析后的结果是
cJSON
的树状的数据结构,一旦解析成功,就有责任在使用完后使用cJSON_Delete
释放内存
默认分配内存使用的是
malloc
函数,释放内存使用的是free
函数。但是可以使用cJSON_InitHooks
函数来全局性改变
当一个错误发生时,
cJSON_GetErrorPtr
函数可以得到指向输入字符串中错误的位置的指针。值的注意的是在多线程的情况下,该函数会产生竞争条件,更好的方法是使用带有return_parse_end
参数的cJSON_ParseWithOpts
函数。
如果你想有更多的选项,使用
cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated)
函数,return_parse_end
返回输入的JSON字符串的结尾或者一个错误发生的地方(从而在保障线程安全的情况下替换cJSON_GetErrorPtr
函数)。require_null_terminated
如果该值设为1,那么当输入的字符串在有效的以’\0’结尾的json字符串后还包含其他的数据,就会报错
打印JSON
使用
cJSON_Print
函数将一个cJSON
数据结构打印为字符串
char *string = cJSON_Print(json);
该函数将会动态分配内存给一个字符串,将JSON表达式放入其中。一旦该函数返回,就有责任释放该内存(默认是
free
,取决于设置的cJSON_InitHooks
)
cJSON_Print
将会用空白符来格式化JSON字符串。可以使用cJSON_PrintUnformatted
来无格式化的打印
如果你关于返回的结果的字符串的大小有一个想法,你可以使用
cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt)
函数。fmt
是一个决定是否用空白字符格式化JSON字符串,prebuffer
指出了所用的第一个缓冲区大小。cJOSN_Print
当前使用256字节的缓冲区大小。一旦打印超过了大小,新的缓冲区会被动态分配,在继续打印之前旧的缓冲区里的内容复制到新的缓冲区里。
使用
cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format)
函数可以完全避免动态的内存分配,该函数需要指向缓冲区的指针和该缓冲区的大小,如果缓冲区过小,打印将会失败,函数返回0。一旦成功,函数返回1。值得注意的是需要准备超过实际需要的字节还要多5个字节,因为cJSON并不是100%的精确估计提供的内存是否足够
示例
在这个例子里,我们想去构建一个JSON并解析它
{
"name": "Awesome 4K",
"resolutions": [
{
"width": 1280,
"height": 720
},
{
"width": 1920,
"height": 1080
},
{
"width": 3840,
"height": 2160
}
]
}
构建以上的json,然后打印成字符串
//create a monitor with a list of supported resolutions
char* create_monitor(void)
{
const unsigned int resolution_numbers[3][2] = {
{1280, 720},
{1920, 1080},
{3840, 2160}
};
char *string = NULL;
cJSON *name = NULL;
cJSON *resolutions = NULL;
cJSON *resolution = NULL;
cJSON *width = NULL;
cJSON *height = NULL;
size_t index = 0;
cJSON *monitor = cJSON_CreateObject();
if (monitor == NULL)
{
goto end;
}
name = cJSON_CreateString("Awesome 4K");
if (name == NULL)
{
goto end;
}
/* after creation was successful, immediately add it to the monitor,
* thereby transfering ownership of the pointer to it */
cJSON_AddItemToObject(monitor, "name", name);
resolutions = cJSON_CreateArray();
if (resolutions == NULL)
{
goto end;
}
cJSON_AddItemToObject(monitor, "resolutions", resolutions);
for (index = 0; index < (sizeof(resolution_numbers) / (2 * sizeof(int))); ++index)
{
resolution = cJSON_CreateObject();
if (resolution == NULL)
{
goto end;
}
cJSON_AddItemToArray(resolutions, resolution);
width = cJSON_CreateNumber(resolution_numbers[index][0]);
if (width == NULL)
{
goto end;
}
cJSON_AddItemToObject(resolution, "width", width);
height = cJSON_CreateNumber(resolution_numbers[index][1]);
if (height == NULL)
{
goto end;
}
cJSON_AddItemToObject(resolution, "height", height);
}
string = cJSON_Print(monitor);
if (string == NULL)
{
fprintf(stderr, "Failed to print monitor.\n");
}
end:
cJSON_Delete(monitor);
return string;
}
我们可以使用cJSON_Add...ToObject
辅助函数来更方便构建
char *create_monitor_with_helpers(void)
{
const unsigned int resolution_numbers[3][2] = {
{1280, 720},
{1920, 1080},
{3840, 2160}
};
char *string = NULL;
cJSON *resolutions = NULL;
size_t index = 0;
cJSON *monitor = cJSON_CreateObject();
if (cJSON_AddStringToObject(monitor, "name", "Awesome 4K") == NULL)
{
goto end;
}
resolutions = cJSON_AddArrayToObject(monitor, "resolutions");
if (resolutions == NULL)
{
goto end;
}
for (index = 0; index < (sizeof(resolution_numbers) / (2 * sizeof(int))); ++index)
{
cJSON *resolution = cJSON_CreateObject();
if (cJSON_AddNumberToObject(resolution, "width", resolution_numbers[index][0]) == NULL)
{
goto end;
}
if(cJSON_AddNumberToObject(resolution, "height", resolution_numbers[index][1]) == NULL)
{
goto end;
}
cJSON_AddItemToArray(resolutions, resolution);
}
string = cJSON_Print(monitor);
if (string == NULL) {
fprintf(stderr, "Failed to print monitor.\n");
}
end:
cJSON_Delete(monitor);
return string;
}
解析并测试
/* return 1 if the monitor supports full hd, 0 otherwise */
int supports_full_hd(const char * const monitor)
{
const cJSON *resolution = NULL;
const cJSON *resolutions = NULL;
const cJSON *name = NULL;
int status = 0;
cJSON *monitor_json = cJSON_Parse(monitor);
if (monitor_json == NULL)
{
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr != NULL)
{
fprintf(stderr, "Error before: %s\n", error_ptr);
}
status = 0;
goto end;
}
name = cJSON_GetObjectItemCaseSensitive(monitor_json, "name");
if (cJSON_IsString(name) && (name->valuestring != NULL))
{
printf("Checking monitor \"%s\"\n", name->valuestring);
}
resolutions = cJSON_GetObjectItemCaseSensitive(monitor_json, "resolutions");
cJSON_ArrayForEach(resolution, resolutions)
{
cJSON *width = cJSON_GetObjectItemCaseSensitive(resolution, "width");
cJSON *height = cJSON_GetObjectItemCaseSensitive(resolution, "height");
if (!cJSON_IsNumber(width) || !cJSON_IsNumber(height))
{
status = 0;
goto end;
}
if ((width->valuedouble == 1920) && (height->valuedouble == 1080))
{
status = 1;
goto end;
}
}
end:
cJSON_Delete(monitor_json);
return status;
}
cJSON_Parse
函数的返回结果没有空指针的检查,因为cJSON_GetObjectItemCaseSensitive
对于输入空指针有检查