cJSON详解
笔者在以前的博客中介绍了C语言的简单数据解析,此方法适用于一些简单封装的数据,如坐标信息,物体抽象信息等。并且解析后的数据结构也比较单一,只有字符型和整型基本数据类型。而在一些大型嵌入式工程中,单位传输数据的体量和数据类型的复杂度都是非常庞大的。这时,我们需要引入JSON这一适用广泛的数据格式。
JSON数据格式简介
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,不是一种编程语言。JSON采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得JSON成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成,并有效地提升传输效率。
JSON语法
JSON的基本数据格式为键值对,中间由:
隔开(牢记这一点),键也可以理解为名称,本身必须是字符串。值可以是基本数据类型:字符串string
、数值number
、布尔值true
或false
和空null
。高级数据类型:数组array
使用[]
表示和对象object
使用{}
表示。
JSON语法有如下特点
-
数据在键值对中
-
数据由逗号分隔
-
方括号保存数组
-
花括号保存对象
例
{
"students" : [ {
"name" : "XiaoMing",
"age" : 10,
"learning" : true
}, {
"name" : "XiaoHong",
"age" : 11,
"learning" : false
} ]
}
- 该JSON语句最外层有一组花括号,故为一个对象
object
。该对象可以配合上层的键组成键值对,也可以插入到上层的数组中,组成更加复杂的JSON数据。 - 该对象里有一个键值对,键(名称)是students,值为数组
array
类型。该数组含有两个**对象object
**类型的元素,使用,
分隔。 - 每个对象类型的数组元素里有三个键值对,类型分别为字符串
string
、数值number
、布尔值true
或false
类型。在第一个元素中字符串string
类型的键是name,其值是XiaoMing字符串,以此类推。 - 通过遍历该JSON语句,可以获得任意键(名称)所对应的值(数据)。
cJSON概述
在JavaScript中,内置了JSON的解析器,可以直接使用JSON。但在c语言中并没有内置JSON相关的库,我们可以根据JSON语法自己写一个JSON解析器。当然也可以引用外部的JSON库来进行解析操作,这些库称为cJSON库。
cJSON库
开源的cJSON库很多,笔者使用的cJSON库地址
打开下载后的文件夹,在此目录下可以看到cJSON.c
和cJSON.h
文件。这两个文件是cJSON库的源码文件和头文件,在工程中包含这两个文件就可以使用该cJSON库。
使用方法
具体的使用方法在README.md
文档中详细介绍,英文好的同学可自行学习,本节简要讲解cJSON库常用的两种使用方法。
直接使用源码文件
以cJSON库目录下测试文件test.c
为例。
使用如下命令即可编译,生成的文件可直接运行。
gcc test.c cJSON.c cJSON.h -o test.exe # Windows环境
gcc test.c cJSON.c cJSON.h -o test # Linux环境
将cJSON.c
和cJSON.h
文件直接复制到工程,这种方法最为便捷。
安装至系统目录下(Linux环境)
在cJSON库目录下,执行如下命令:
make
sudo make install
系统会将头文件安装至/usr/local/include/cjson
目录下,库文件安装至/usr/local/lib
目录下。
在使用时#include <cjson/cJSON.h>
包含头文件,并在编译时-lcjson
链接库。
以cJSON库目录下测试文件test.c
为例。
-
将test.c文件中
#include "cJSON.h"
替换为#include <cjson/cJSON.h>
。 -
使用
gcc test.c -o test -lcjson
编译,生成的文件可直接运行。
cJSON结构体及宏定义
本节主要讲解cJSON.h中定义的结构体和宏定义,对于一些cJSON内部使用的结构体和宏定义不做深入讲解。
cJSON结构体
cJSON最为核心的就是cJSON结构体:
/* The cJSON structure: */
typedef struct cJSON
{
struct cJSON *next; //后置节点指针
struct cJSON *prev; //前置节点指针
struct cJSON *child; //子节点指针
int type; //值 的类型
char *valuestring; //字符串数据
int valueint; //整型数据
double valuedouble; //浮点型数据
char *string; //名 的名称
} cJSON;
可见, cJOSN的核心采用了双向链表,通过*next
和*prev
寻找目标节点,实现添加和删除操作。而且加入了*child
子节点指针,可以实现更加复杂的层级结构。
type
代表值的类型,其取值在如下结构体中定义:
/* cJSON Types: */
#define cJSON_Invalid (0)
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw (1 << 7) /* raw json */
通过读取type
的值,可以判断该结构体内哪种类型的值有效,进而操作对应的数据。
cJSON_Hooks结构体
cJSON支持自定义内存管理函数,其结构体定义如下所示:
typedef struct cJSON_Hooks
{
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
此结构体内有两个函数指针,通过名字我们可以知道这就是内存申请和释放的函数指针。通过编写自定义的内存管理函数对该结构体赋值,调用cJSON_InitHooks
函数对global_hooks
全局变量赋值,该全局变量类型及初始化如下:
typedef struct internal_hooks
{
void *(CJSON_CDECL *allocate)(size_t size);
void (CJSON_CDECL *deallocate)(void *pointer);
void *(CJSON_CDECL *reallocate)(void *pointer, size_t size);
} internal_hooks;
static internal_hooks global_hooks = {
internal_malloc,
internal_free,
internal_realloc
};
internal_hooks
结构体成员也是内存管理的函数指针,不同于cJSON_InitHooks
,它多一个reallocate
成员。在global_hooks
全局变量中,其成员通过宏定义全部初始化为默认的内存管理函数,即malloc
、free
、realloc
。internal_realloc
无法通过cJSON_InitHooks
函数重新定义。
接下来cJSON库会调用global_hooks
全局变量里面的内存管理函数。
例:在cJSON_ParseWithLengthOpts
函数中,使用item = cJSON_New_Item(&global_hooks);
语句创建一个节点指针,cJSON_New_Item
原型如下:
/* Internal constructor. */
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;
}
由此可知,cJSON_New_Item
通过调用global_hooks
中的allocate
成员申请内存。
其他宏定义
版本信息
/* project version */
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 7
#define CJSON_VERSION_PATCH 14
调用cJSON_Version
函数可打印此信息,有些cJSON库可能不兼容过低版本的库,会导致解析失败。
嵌套深度
#define CJSON_NESTING_LIMIT 1000
为了防止堆栈溢出做出的保护措施,在一些小型嵌入式系统中可以给改此值。
cJSON应用函数
本节主要讲解cJSON.h中定义的函数。
cJSON主要函数
cJSON_Parse
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
将cJSON字符串格式化为cJSON链表,并返回指向此链表的指针。
cJSON_Delete
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
释放位于堆中的cJSON结构体内存。使用cJSON_Parse
或Create
类的函数初始化的cJSON结构体,在不使用时须调用cJSON_Delete
进行释放,否则会导致内存泄漏。形成链表关系时,调用此函数释放根节点时会释放整条链表,注意不要double free
。
cJSON_Print
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
将cJSON链表解析成带格式的JSON字符串,并返回指向位于堆中的该字符串的指针。不使用时须调用free
释放该内存。
cJSON组包函数
创建指定类型的JSON对象
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
添加JSON对象到JSON链表中
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
创建JSON对象并添加到JSON链表中
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
cJSON解包函数
获取JSON对象
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
获取JSON数据
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
判断cJSON对象数据类型
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
从cJSON链表中删除一个cJSON数据
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
cJSON应用示例
cJSON组包操作
#include <stdio.h>
#include <cjson/cJSON.h>
int main(int argc, char **argv)
{
cJSON *root = cJSON_CreateObject();
cJSON *students = cJSON_CreateArray();
cJSON *students1 = cJSON_CreateObject();
cJSON *students2 = cJSON_CreateObject();
students = cJSON_AddArrayToObject(root, "students");
cJSON_AddItemToArray(students, students1);
cJSON_AddItemToArray(students, students2);
cJSON_AddStringToObject(students1, "name", "XiaoMing");
cJSON_AddNumberToObject(students1, "age", 10);
cJSON_AddTrueToObject(students1, "learning");
cJSON_AddStringToObject(students2, "name", "XiaoHong");
cJSON_AddNumberToObject(students2, "age", 11);
cJSON_AddFalseToObject(students2, "learning");
printf("%s\n", cJSON_Print(root));
cJSON_Delete(root);
return 0;
}
执行结果
{
"students": [{
"name": "XiaoMing",
"age": 10,
"learning": true
}, {
"name": "XiaoHong",
"age": 11,
"learning": false
}]
}
cJSON解包操作
#include <stdio.h>
#include <cjson/cJSON.h>
int main(int argc, char **argv)
{
const char *str = "{\"students\":[{\"name\" : \"XiaoMing\", \"age\" : 10, \"learning\" : true},{\"name\" : \"XiaoHong\", \"age\" : 11, \"learning\" : false}]}";
cJSON *root = NULL;
cJSON *students = NULL, *students1 = NULL, *students2 = NULL;
cJSON *name1 = NULL, *age1 = NULL, *learning1 = NULL;
cJSON *name2 = NULL, *age2 = NULL, *learning2 = NULL;
root = cJSON_Parse(str);
if (root == NULL){
printf("cJSON ERROR : %s\n", cJSON_GetErrorPtr());
}
students = cJSON_GetObjectItem(root, "students");
students1 = cJSON_GetArrayItem(students, 0);
students2 = cJSON_GetArrayItem(students, 1);
name1 = cJSON_GetObjectItem(students1, "name");
age1 = cJSON_GetObjectItem(students1, "age");
learning1 = cJSON_GetObjectItem(students1, "learning");
name2 = cJSON_GetObjectItem(students2, "name");
age2 = cJSON_GetObjectItem(students2, "age");
learning2 = cJSON_GetObjectItem(students2, "learning");
printf("%s\n", cJSON_Print(root));
printf("name --> %s\n", cJSON_GetStringValue(name1));
printf("age --> %0.f\n", cJSON_GetNumberValue(age1));
printf("learning --> %d\n", cJSON_IsTrue(learning1));
printf("name --> %s\n", cJSON_GetStringValue(name2));
printf("age --> %0.f\n", cJSON_GetNumberValue(age2));
printf("learning --> %d\n", cJSON_IsTrue(learning2));
cJSON_Delete(root);
return 0;
}
执行结果
{
"students": [{
"name": "XiaoMing",
"age": 10,
"learning": true
}, {
"name": "XiaoHong",
"age": 11,
"learning": false
}]
}
name --> XiaoMing
age --> 10
learning --> 1
name --> XiaoHong
age --> 11
learning --> 0