-
cJSON 是一个超轻量级的 JSON 解析器,由纯 C 语言编写,只有一个
.c文件和一个.h文件,非常适合嵌入式系统和 C/C++ 项目使用。1. 获取与集成
下载源码
cJSON 源码非常简单,只需两个文件:
-
cJSON.c -
cJSON.h
获取方式:
# 通过源码包 wget https://github.com/DaveGamble/cJSON/archive/refs/tags/v1.7.15.tar.gz # 或通过 git git clone https://github.com/DaveGamble/cJSON.git集成到项目
方法一:直接复制(推荐小型项目)
project/ ├── main.c ├── cJSON.c └── cJSON.h编译时一起编译:
gcc main.c cJSON.c -o myprogram -lm方法二:编译为库
gcc -c cJSON.c -o cJSON.o ar rcs libcjson.a cJSON.o # 链接时使用:gcc main.c -L. -lcjson -lm -o myprogram2. 基础概念
cJSON 使用链表结构存储 JSON 数据,核心结构体:
typedef struct cJSON { struct cJSON *next; // 同级下一个节点(链表) struct cJSON *prev; // 同级上一个节点 struct cJSON *child; // 子节点(对象或数组的第一个子项) int type; // 数据类型(字符串/数字/数组等) char *valuestring; // 当类型为字符串时的值 int valueint; // 整数值 double valuedouble; // 浮点数值 char *string; // 键名(key) } cJSON;支持的类型:
宏 值 说明 cJSON_Invalid0 无效 cJSON_False1 布尔假 cJSON_True2 布尔真 cJSON_NULL3 空值 cJSON_Number4 数字 cJSON_String5 字符串 cJSON_Array6 数组 cJSON_Object7 对象 cJSON_Raw8 原始 JSON 文本 3. 解析 JSON(从字符串到对象)
3.1 基础解析
#include <stdio.h> #include "cJSON.h" int main() { // JSON 字符串 const char *json_str = "{\"name\":\"Alice\",\"age\":25,\"scores\":[85,92,78]}"; // 解析为 cJSON 对象 cJSON *root = cJSON_Parse(json_str); if (root == NULL) { printf("解析失败\n"); return 1; } // 使用 root... // 释放内存(非常重要!) cJSON_Delete(root); return 0; }3.2 获取解析错误位置
const char *error_ptr = cJSON_GetErrorPtr(); if (error_ptr != NULL) { fprintf(stderr, "错误位置: %s\n", error_ptr); }4. 提取数据
4.1 获取对象字段
// 假设 root 是解析后的对象 cJSON *name_obj = cJSON_GetObjectItem(root, "name"); if (cJSON_IsString(name_obj)) { printf("姓名: %s\n", name_obj->valuestring); } cJSON *age_obj = cJSON_GetObjectItem(root, "age"); if (cJSON_IsNumber(age_obj)) { printf("年龄: %d (或 %.1f)\n", age_obj->valueint, age_obj->valuedouble); }4.2 遍历数组
cJSON *scores = cJSON_GetObjectItem(root, "scores"); if (cJSON_IsArray(scores)) { int size = cJSON_GetArraySize(scores); printf("成绩数量: %d\n", size); // 方法一:通过索引访问 for (int i = 0; i < size; i++) { cJSON *item = cJSON_GetArrayItem(scores, i); if (cJSON_IsNumber(item)) { printf("成绩 %d: %d\n", i, item->valueint); } } // 方法二:遍历链表(更快) cJSON *score; int index = 0; cJSON_ArrayForEach(score, scores) { printf("成绩 %d: %d\n", index++, score->valueint); } }4.3 遍历对象所有键值对
cJSON *current_element = NULL; char *current_key = NULL; cJSON_ArrayForEach(current_element, root) { current_key = current_element->string; if (current_key != NULL) { printf("键: %s, 类型: %d\n", current_key, current_element->type); } }5. 创建 JSON(从对象到字符串)
5.1 创建简单对象
// 创建根对象 cJSON *root = cJSON_CreateObject(); // 添加字符串 cJSON_AddStringToObject(root, "name", "Bob"); cJSON_AddStringToObject(root, "email", "bob@example.com"); // 添加数字 cJSON_AddNumberToObject(root, "age", 30); cJSON_AddNumberToObject(root, "score", 95.5); // 添加布尔值 cJSON_AddBoolToObject(root, "is_student", 0); // false // 添加 null cJSON_AddNullToObject(root, "nickname"); // 转换为字符串(格式化输出,带换行缩进) char *json_str = cJSON_Print(root); // 或压缩格式(无换行) // char *json_str = cJSON_PrintUnformatted(root); printf("%s\n", json_str); // 释放内存(注意释放顺序!) free(json_str); // 字符串用 free cJSON_Delete(root); // cJSON 对象用 cJSON_Delete5.2 创建嵌套对象
cJSON *root = cJSON_CreateObject(); // 创建 address 子对象 cJSON *address = cJSON_CreateObject(); cJSON_AddStringToObject(address, "city", "Beijing"); cJSON_AddStringToObject(address, "street", "长安街"); // 将 address 添加到 root cJSON_AddItemToObject(root, "address", address); // 结果: {"address":{"city":"Beijing","street":"长安街"}}5.3 创建数组
cJSON *root = cJSON_CreateObject(); // 创建数组 cJSON *hobbies = cJSON_CreateArray(); // 添加数组元素 cJSON_AddItemToArray(hobbies, cJSON_CreateString("reading")); cJSON_AddItemToArray(hobbies, cJSON_CreateString("swimming")); cJSON_AddItemToArray(hobbies, cJSON_CreateString("coding")); // 添加数字数组 cJSON *scores = cJSON_CreateArray(); int score_values[] = {88, 92, 79, 95}; for (int i = 0; i < 4; i++) { cJSON_AddItemToArray(scores, cJSON_CreateNumber(score_values[i])); } // 添加到根对象 cJSON_AddItemToObject(root, "hobbies", hobbies); cJSON_AddItemToObject(root, "scores", scores);5.4 创建对象数组
cJSON *students = cJSON_CreateArray(); // 学生 1 cJSON *stu1 = cJSON_CreateObject(); cJSON_AddStringToObject(stu1, "name", "Alice"); cJSON_AddNumberToObject(stu1, "id", 1001); cJSON_AddItemToArray(students, stu1); // 学生 2 cJSON *stu2 = cJSON_CreateObject(); cJSON_AddStringToObject(stu2, "name", "Bob"); cJSON_AddNumberToObject(stu2, "id", 1002); cJSON_AddItemToArray(students, stu2); // 添加到根 cJSON_AddItemToObject(root, "students", students);6. 修改 JSON 数据
6.1 替换值
// 替换已有字段的值 cJSON *name = cJSON_GetObjectItem(root, "name"); if (name != NULL) { cJSON_SetValueString(name, "NewName"); } // 替换数字 cJSON *age = cJSON_GetObjectItem(root, "age"); if (age != NULL) { cJSON_SetNumberValue(age, 35); }6.2 删除字段
// 从对象中删除某个键 cJSON_DeleteItemFromObject(root, "nickname"); // 从数组中删除索引为 2 的元素 cJSON_DeleteItemFromArray(scores, 2);6.3 添加/插入
// 对象添加新字段(已有会追加) cJSON_AddStringToObject(root, "new_field", "new_value"); // 数组插入(在索引 0 处插入) cJSON *new_item = cJSON_CreateString("first_item"); cJSON_InsertItemInArray(array, 0, new_item);7. 高级功能
7.1 比较两个 JSON
cJSON *obj1 = cJSON_Parse("{\"a\":1,\"b\":2}"); cJSON *obj2 = cJSON_Parse("{\"a\":1,\"b\":2}"); if (cJSON_Compare(obj1, obj2, cJSON_True)) { // cJSON_True 表示严格比较 printf("相等\n"); } else { printf("不相等\n"); } cJSON_Delete(obj1); cJSON_Delete(obj2);7.2 深拷贝
cJSON *original = cJSON_Parse("{\"data\":\"secret\"}"); cJSON *copy = cJSON_Duplicate(original, cJSON_True); // True 表示递归深拷贝 // 修改 copy 不会影响 original cJSON_ReplaceItemInObject(copy, "data", cJSON_CreateString("modified")); cJSON_Delete(original); cJSON_Delete(copy);7.3 处理大型 JSON(流式解析)
cJSON 默认将所有数据加载到内存,对于超大 JSON,需要分批处理或使用其他库如 json-c 的流式接口。但 cJSON 提供了
cJSON_PrintBuffered控制输出缓冲:// 使用 1024 字节缓冲区打印 char *str = cJSON_PrintBuffered(root, 1024, 0); // 0=非格式化,1=格式化 free(str);8. 内存管理详解
8.1 分配与释放规则
函数 分配方式 释放方式 cJSON_Parse()内部 malloc cJSON_Delete()cJSON_CreateXxx()内部 malloc cJSON_Delete()cJSON_Print()/cJSON_PrintUnformatted()内部 malloc free()cJSON_PrintBuffered()内部 malloc free()cJSON_Duplicate()内部 malloc cJSON_Delete()8.2 内存泄漏检查示例
void process_json(const char *input) { cJSON *root = cJSON_Parse(input); if (root == NULL) return; cJSON *data = cJSON_GetObjectItem(root, "data"); if (data != NULL) { // 注意:不要释放 data!它是 root 的一部分 // cJSON_Delete(data); // ❌ 错误!会导致双重释放 } // 只需要释放根节点,所有子节点会自动递归释放 cJSON_Delete(root); // ✅ 正确 }8.3 自定义内存管理钩子(可选)
// 自定义 malloc/free/realloc 用于嵌入式系统 cJSON_Hooks hooks; hooks.malloc_fn = my_custom_malloc; hooks.free_fn = my_custom_free; cJSON_InitHooks(&hooks);9. 完整实战示例
示例:配置文件读写
#include <stdio.h> #include <stdlib.h> #include "cJSON.h" // 读取文件内容 char* read_file(const char *filename) { FILE *f = fopen(filename, "rb"); if (!f) return NULL; fseek(f, 0, SEEK_END); long len = ftell(f); fseek(f, 0, SEEK_SET); char *data = malloc(len + 1); fread(data, 1, len, f); data[len] = '\0'; fclose(f); return data; } // 写入文件 void write_file(const char *filename, const char *data) { FILE *f = fopen(filename, "w"); if (f) { fputs(data, f); fclose(f); } } // 创建默认配置 void create_default_config(const char *filename) { cJSON *config = cJSON_CreateObject(); cJSON_AddStringToObject(config, "version", "1.0.0"); cJSON_AddNumberToObject(config, "port", 8080); cJSON_AddBoolToObject(config, "debug_mode", 0); // 数据库配置 cJSON *database = cJSON_CreateObject(); cJSON_AddStringToObject(database, "host", "localhost"); cJSON_AddNumberToObject(database, "port", 3306); cJSON_AddStringToObject(database, "username", "admin"); cJSON_AddStringToObject(database, "password", "secret123"); cJSON_AddItemToObject(config, "database", database); // 功能开关数组 cJSON *features = cJSON_CreateArray(); cJSON_AddItemToArray(features, cJSON_CreateString("logging")); cJSON_AddItemToArray(features, cJSON_CreateString("caching")); cJSON_AddItemToArray(features, cJSON_CreateString("monitoring")); cJSON_AddItemToObject(config, "enabled_features", features); char *str = cJSON_Print(config); write_file(filename, str); free(str); cJSON_Delete(config); printf("已创建默认配置文件\n"); } // 读取并修改配置 void modify_config(const char *filename) { char *content = read_file(filename); if (!content) { printf("无法读取文件\n"); return; } cJSON *config = cJSON_Parse(content); free(content); if (!config) { printf("解析失败: %s\n", cJSON_GetErrorPtr()); return; } // 读取配置 cJSON *port = cJSON_GetObjectItem(config, "port"); if (cJSON_IsNumber(port)) { printf("当前端口: %d\n", port->valueint); } // 修改端口 cJSON_SetNumberValue(port, 9090); // 添加新字段 cJSON_AddStringToObject(config, "last_modified", "2024-01-15"); // 修改嵌套对象 cJSON *db = cJSON_GetObjectItem(config, "database"); if (db) { cJSON_ReplaceItemInObject(db, "password", cJSON_CreateString("new_secure_pass")); } // 添加新特性 cJSON *features = cJSON_GetObjectItem(config, "enabled_features"); if (cJSON_IsArray(features)) { cJSON_AddItemToArray(features, cJSON_CreateString("ssl")); } // 保存回文件 char *new_str = cJSON_Print(config); write_file(filename, new_str); free(new_str); cJSON_Delete(config); printf("配置已更新\n"); } int main() { const char *config_file = "app_config.json"; // 首次运行创建配置 create_default_config(config_file); // 修改配置 modify_config(config_file); return 0; }10. 最佳实践与注意事项
✅ 推荐做法
-
始终检查返回值
cJSON *item = cJSON_GetObjectItem(root, "key"); if (item != NULL && cJSON_IsString(item)) { ... } -
使用宏判断类型
-
cJSON_IsObject(item) -
cJSON_IsArray(item) -
cJSON_IsString(item) -
cJSON_IsNumber(item) -
cJSON_IsBool(item) -
cJSON_IsNull(item)
-
-
处理数字时注意精度
-
整数:使用
valueint -
浮点数:使用
valuedouble -
大整数(超过 2^53):作为字符串处理
-
⚠️ 常见陷阱
-
重复释放:不要单独释放子节点
-
内存泄漏:确保每个
Parse/Create都有对应的Delete -
字符串转义:cJSON 会自动处理特殊字符转义,无需手动转义
-
UTF-8:cJSON 完全支持 UTF-8,但需确保输入是合法 UTF-8 编码
性能提示
-
频繁创建/删除时,考虑对象池复用内存
-
超大 JSON 避免使用
cJSON_Print,改用流式输出 -
使用
cJSON_ParseWithOpts解析部分 JSON(带长度参数)
11. 编译与调试
Makefile 示例
CC = gcc CFLAGS = -Wall -Wextra -O2 LDFLAGS = -lm all: myapp myapp: main.c cJSON.c $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) debug: CFLAGS = -Wall -Wextra -g -O0 -DDEBUG debug: myapp clean: rm -f myapp .PHONY: all clean debug调试技巧
// 打印整个结构(调试用) void print_cjson_tree(cJSON *item, int depth) { if (!item) return; char indent[50] = {0}; for (int i = 0; i < depth && i < 49; i++) indent[i] = ' '; printf("%s[type=%d, key=%s, val=%s]\n", indent, item->type, item->string ? item->string : "(null)", cJSON_IsString(item) ? item->valuestring : "(num/obj)"); if (item->child) print_cjson_tree(item->child, depth + 2); if (item->next) print_cjson_tree(item->next, depth); } -
浙公网安备 33010602011771号