• cJSON 完整使用教程

    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 myprogram

    2. 基础概念

    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_Invalid 0 无效
    cJSON_False 1 布尔假
    cJSON_True 2 布尔真
    cJSON_NULL 3 空值
    cJSON_Number 4 数字
    cJSON_String 5 字符串
    cJSON_Array 6 数组
    cJSON_Object 7 对象
    cJSON_Raw 8 原始 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_Delete

    5.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. 最佳实践与注意事项

    ✅ 推荐做法

    1. 始终检查返回值

      cJSON *item = cJSON_GetObjectItem(root, "key");
      if (item != NULL && cJSON_IsString(item)) { ... }
    2. 使用宏判断类型

      • cJSON_IsObject(item)

      • cJSON_IsArray(item)

      • cJSON_IsString(item)

      • cJSON_IsNumber(item)

      • cJSON_IsBool(item)

      • cJSON_IsNull(item)

    3. 处理数字时注意精度

      • 整数:使用 valueint

      • 浮点数:使用 valuedouble

      • 大整数(超过 2^53):作为字符串处理

    ⚠️ 常见陷阱

    1. 重复释放:不要单独释放子节点

    2. 内存泄漏:确保每个 Parse/Create 都有对应的 Delete

    3. 字符串转义:cJSON 会自动处理特殊字符转义,无需手动转义

    4. 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);
    }

     

posted on 2026-04-03 16:52  轩~邈  阅读(97)  评论(0)    收藏  举报