解析CJSON嵌套结构:从入门到精通
在嵌入式开发、网络通信等领域,JSON(JavaScript Object Notation)因其轻量、易读的特性成为数据交换的主流格式,而在C语言生态中,cJSON是一款广泛使用的轻量级JSON解析库,它以简洁的API和高效的内存管理著称,实际应用中JSON数据往往具有复杂的嵌套结构(如对象嵌套对象、数组嵌套数组等),如何高效解析这些嵌套数据是开发者必须的核心技能,本文将结合实例,系统讲解cJSON嵌套结构的解析方法,从基础API到高级技巧,助你彻底攻克嵌套解析难题。
cJSON嵌套数据结构基础
要解析嵌套的JSON数据,首先需要理解cJSON如何通过其核心结构体表示嵌套关系,cJSON的核心是一个链表节点,每个节点通过type字段标识数据类型(如对象、数组、字符串、数字等),并通过child指针指向子节点,从而形成嵌套结构。
核心结构体与字段
typedef struct cJSON {
struct cJSON *next; // 同级节点(链表下一个节点)
struct cJSON *prev; // 同级节点(链表上一个节点)
struct cJSON *child; // 子节点(用于对象/数组的嵌套)
int type; // 节点类型(如 cJSON_Object、cJSON_Array 等)
char *valuestring; // 字符串值(type 为 cJSON_String 时)
double valuedouble; // 数值(type 为 cJSON_Number 时)
char *string; // 键名(type 为 cJSON_Object 时)
} cJSON;
嵌套结构的两种典型场景
- 对象嵌套对象:如
{"user": {"name": "Alice", "age": 25}},外层对象的child指向一个type=cJSON_Object的节点,该节点的child又指向name和age两个子节点。 - 数组嵌套数组/对象:如
{"data": [{"id": 1}, {"id": 2}]},外层数组的child指向两个type=cJSON_Object的节点,每个对象节点包含id字段。
解析嵌套JSON的通用步骤
无论嵌套多复杂,解析流程均可概括为“加载字符串 → 定位根节点 → 逐层遍历子节点”,以下是具体步骤:
加载JSON字符串
使用cJSON_Parse()函数将JSON字符串解析为cJSON对象,返回根节点指针:
#include "cJSON.h"
const char *json_str = "{\"user\": {\"name\": \"Alice\", \"age\": 25}, \"hobbies\": [\"reading\", \"coding\"]}";
cJSON *root = cJSON_Parse(json_str);
if (!root) {
printf("JSON解析失败:%s\n", cJSON_GetErrorPtr());
return -1;
}
定位目标父节点
通过cJSON_GetObjectItem()根据键名获取嵌套结构的父节点(如对象或数组):
// 获取"user"对象节点(嵌套在根节点下)
cJSON *user_obj = cJSON_GetObjectItem(root, "user");
if (!user_obj || user_obj->type != cJSON_Object) {
printf("未找到user对象或类型错误\n");
cJSON_Delete(root);
return -1;
}
// 获取"hobbies"数组节点
cJSON *hobbies_array = cJSON_GetObjectItem(root, "hobbies");
if (!hobbies_array || hobbies_array->type != cJSON_Array) {
printf("未找到hobbies数组或类型错误\n");
cJSON_Delete(root);
return -1;
}
遍历子节点
根据父节点类型(对象或数组),通过child指针或数组遍历API访问子节点:
场景1:遍历对象嵌套对象(如user_obj的子节点)
// 遍历user_obj的所有子节点(键值对)
cJSON *user_child = user_obj->child;
while (user_child != NULL) {
if (strcmp(user_child->string, "name") == 0) {
// 获取字符串值
printf("name: %s\n", user_child->valuestring);
} else if (strcmp(user_child->string, "age") == 0) {
// 获取数值
printf("age: %d\n", (int)user_child->valuedouble);
}
user_child = user_child->next; // 移动到同级下一个节点
}
场景2:遍历数组嵌套对象/数组(如hobbies_array的子节点)
// 遍历hobbies_array的所有元素(数组下标从0开始)
int hobbies_count = cJSON_GetArraySize(hobbies_array);
for (int i = 0; i < hobbies_count; i++) {
cJSON *hobby_item = cJSON_GetArrayItem(hobbies_array, i);
if (hobby_item->type == cJSON_String) {
printf("hobby[%d]: %s\n", i, hobby_item->valuestring);
}
}
释放内存
解析完成后,必须使用cJSON_Delete()释放整个cJSON树占用的内存,避免内存泄漏:
cJSON_Delete(root);
典型嵌套场景解析实例
实例1:多层对象嵌套
假设JSON数据为:
{
"school": {
"name": "清华大学",
"departments": [
{
"dept_name": "计算机系",
"students": [
{"name": "Bob", "grade": 90},
{"name": "Charlie", "grade": 85}
]
}
]
}
}
解析目标:获取“计算机系”第一个学生的姓名和成绩。
const char *json_str = "{\"school\": {\"name\": \"清华大学\",\"departments\": [{\"dept_name\": \"计算机系\",\"students\": [{\"name\": \"Bob\",\"grade\": 90},{\"name\": \"Charlie\",\"grade\": 85}]}]}}";
cJSON *root = cJSON_Parse(json_str);
// 1. 定位school对象
cJSON *school = cJSON_GetObjectItem(root, "school");
// 2. 定位departments数组
cJSON *depts = cJSON_GetObjectItem(school, "departments");
// 3. 获取departments数组的第一个元素(计算机系对象)
cJSON *dept = cJSON_GetArrayItem(depts, 0);
// 4. 定位students数组
cJSON *students = cJSON_GetObjectItem(dept, "students");
// 5. 获取students数组的第一个元素(Bob对象)
cJSON *bob = cJSON_GetArrayItem(students, 0);
// 6. 获取name和grade
printf("学生姓名: %s, 成绩: %d\n",
cJSON_GetObjectItem(bob, "name")->valuestring,
(int)cJSON_GetObjectItem(bob, "grade")->valuedouble);
cJSON_Delete(root);
实例2:数组嵌套数组
假设JSON数据为:
{
"matrix": [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
}
解析目标:输出所有矩阵元素。
cJSON *root = cJSON_Parse("{\"matrix\": [[1,2,3],[4,5,6],[7,8,9]]}");
cJSON *matrix = cJSON_GetObjectItem(root, "matrix");
// 遍历外层数组(行)
int rows = cJSON_GetArraySize(matrix);
for (int i = 0; i < rows; i++) {
cJSON *row = cJSON_GetArrayItem(matrix, i);
// 遍历内层数组(列)
int cols = cJSON_GetArraySize(row);
for (int j = 0; j < cols; j++) {
cJSON *element = cJSON_GetArrayItem(row, j);
printf("%d ", (int)element->valuedouble);
}
printf("\n");
}
cJSON_Delete(root);
输出:
1 2 3
4 5 6
7 8 9
嵌套解析的常见问题与解决方案
如何判断节点是否存在或类型正确?
直接使用cJSON_GetObjectItem()或cJSON_GetArrayItem()时,若节点不存在会返回NULL,因此必须检查返回值并验证类型:
cJSON *user = cJSON_GetObjectItem(root, "user");
if (!user || user->type != cJSON_Object) {
printf("user节点不存在或不是对象\n");
return;
}
如何处理动态深度的嵌套?
若JSON嵌套深度不确定(如



还没有评论,来说两句吧...