C语言中 cJSON 使用详细教程

cJSON是一个轻量级的JSON解析库,用C语言编写,非常适合在嵌入式系统和资源受限的环境中使用。本教程将详细介绍cJSON库的安装、基本概念、核心函数以及各种使用场景。

1. cJSON简介

cJSON是一个开源的C语言JSON解析库,具有以下特点:

  • 轻量级:代码简洁,易于集成
  • 单文件:只有一个.c和一个.h文件
  • 易于使用:API简单直观
  • 高效:解析和生成速度快

2. 安装与配置

2.1 下载cJSON

可以从GitHub仓库下载cJSON:

git clone https://github.com/DaveGamble/cJSON.git

2.2 编译与安装

cd cJSON
mkdir build
cd build
cmake ..
make
sudo make install

2.3 在项目中使用

在项目中包含头文件并链接库:

#include "cjson/cJSON.h"

编译时链接cJSON库:

gcc your_file.c -lcjson -o your_program

3. 基本概念

3.1 cJSON数据结构

cJSON使用链表结构来表示JSON数据:

typedef struct cJSON {
    struct cJSON *next, *prev;  // 链表指针
    struct cJSON *child;        // 子节点
    int type;                   // 数据类型
    char *valuestring;          // 字符串值
    int valueint;               // 整数值
    double valuedouble;         // 浮点数值
    char *string;               // 键名
} cJSON;

3.2 数据类型

cJSON定义了以下数据类型:

  • cJSON_False: 布尔值false
  • cJSON_True: 布尔值true
  • cJSON_NULL: null值
  • cJSON_Number: 数字
  • cJSON_String: 字符串
  • cJSON_Array: 数组
  • cJSON_Object: 对象

4. 核心函数详解

4.1 JSON解析函数

cJSON_Parse()
cJSON* cJSON_Parse(const char *value);
  • 功能:解析JSON字符串
  • 参数:JSON字符串
  • 返回:解析后的cJSON对象,失败返回NULL
const char *json_str = "{\"name\":\"张三\",\"age\":25,\"city\":\"北京\"}";
cJSON *root = cJSON_Parse(json_str);
if (root == NULL) {
    printf("JSON解析失败\n");
    return -1;
}
cJSON_ParseWithOpts()
cJSON* cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
  • 功能:带选项的JSON解析
  • 参数:
    • value: JSON字符串
    • return_parse_end: 解析结束位置
    • require_null_terminated: 是否要求字符串以null结尾
const char *json_str = "{\"name\":\"张三\",\"age\":25}";
const char *parse_end = NULL;
cJSON *root = cJSON_ParseWithOpts(json_str, &parse_end, true);
if (root == NULL) {
    printf("解析失败,错误位置:%s\n", parse_end);
    return -1;
}

4.2 JSON生成函数

cJSON_Print()
char* cJSON_Print(const cJSON *item);
  • 功能:将cJSON对象转换为格式化的JSON字符串
  • 参数:cJSON对象
  • 返回:格式化的JSON字符串
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "name", "张三");
cJSON_AddNumberToObject(root, "age", 25);

char *json_str = cJSON_Print(root);
printf("JSON字符串:%s\n", json_str);
free(json_str);  // 记得释放内存
cJSON_PrintUnformatted()
char* cJSON_PrintUnformatted(const cJSON *item);
  • 功能:将cJSON对象转换为紧凑的JSON字符串(无格式化)
char *json_str = cJSON_PrintUnformatted(root);
printf("紧凑JSON:%s\n", json_str);
free(json_str);
cJSON_PrintBuffered()
char* cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
  • 功能:预分配缓冲区的打印函数
  • 参数:
    • item: cJSON对象
    • prebuffer: 预分配缓冲区大小
    • fmt: 是否格式化
char *json_str = cJSON_PrintBuffered(root, 1024, true);
printf("缓冲区打印:%s\n", json_str);
free(json_str);

4.3 对象创建函数

cJSON_CreateObject()
cJSON* cJSON_CreateObject(void);
  • 功能:创建JSON对象
cJSON *obj = cJSON_CreateObject();
if (obj == NULL) {
    printf("创建对象失败\n");
    return -1;
}
cJSON_CreateArray()
cJSON* cJSON_CreateArray(void);
  • 功能:创建JSON数组
cJSON *arr = cJSON_CreateArray();
if (arr == NULL) {
    printf("创建数组失败\n");
    return -1;
}

4.4 值添加函数

cJSON_AddItemToObject()
cJSON_bool cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
  • 功能:向对象添加项
cJSON *root = cJSON_CreateObject();
cJSON *name = cJSON_CreateString("张三");
cJSON_AddItemToObject(root, "name", name);
便捷添加函数
// 添加字符串
cJSON_AddStringToObject(cJSON *object, const char *name, const char *string);

// 添加数字
cJSON_AddNumberToObject(cJSON *object, const char *name, double number);

// 添加布尔值
cJSON_AddBoolToObject(cJSON *object, const char *name, int b);

// 添加null
cJSON_AddNullToObject(cJSON *object, const char *name);
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "name", "张三");
cJSON_AddNumberToObject(root, "age", 25.5);
cJSON_AddBoolToObject(root, "married", 1);
cJSON_AddNullToObject(root, "spouse");
数组操作函数
// 向数组添加项
cJSON_AddItemToArray(cJSON *array, cJSON *item);

// 从数组添加项并返回
cJSON* cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);

// 向对象添加数组
cJSON* cJSON_AddArrayToObject(cJSON *object, const char *name);
cJSON *root = cJSON_CreateObject();
cJSON *scores = cJSON_AddArrayToObject(root, "scores");

cJSON *score1 = cJSON_CreateNumber(85);
cJSON *score2 = cJSON_CreateNumber(92);
cJSON *score3 = cJSON_CreateNumber(78);

cJSON_AddItemToArray(scores, score1);
cJSON_AddItemToArray(scores, score2);
cJSON_AddItemToArray(scores, score3);

4.5 查找函数

cJSON_GetObjectItem()
cJSON* cJSON_GetObjectItem(const cJSON * const object, const char * const string);
  • 功能:根据键名获取对象中的项
cJSON *name_item = cJSON_GetObjectItem(root, "name");
if (cJSON_IsString(name_item)) {
    printf("姓名:%s\n", name_item->valuestring);
}
cJSON_GetObjectItemCaseSensitive()
cJSON* cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
  • 功能:大小写敏感的键名查找
cJSON_GetArraySize()
int cJSON_GetArraySize(const cJSON *array);
  • 功能:获取数组大小
cJSON *scores = cJSON_GetObjectItem(root, "scores");
int size = cJSON_GetArraySize(scores);
printf("成绩数量:%d\n", size);
cJSON_GetArrayItem()
cJSON* cJSON_GetArrayItem(const cJSON *array, int index);
  • 功能:根据索引获取数组项
for (int i = 0; i < size; i++) {
    cJSON *score = cJSON_GetArrayItem(scores, i);
    if (cJSON_IsNumber(score)) {
        printf("第%d个成绩:%g\n", i + 1, score->valuedouble);
    }
}

4.6 类型检查函数

cJSON_IsInvalid(const cJSON * const item)
cJSON_IsFalse(const cJSON * const item)
cJSON_IsTrue(const cJSON * const item)
cJSON_IsBool(const cJSON * const item)
cJSON_IsNull(const cJSON * const item)
cJSON_IsNumber(const cJSON * const item)
cJSON_IsString(const cJSON * const item)
cJSON_IsArray(const cJSON * const item)
cJSON_IsObject(const cJSON * const item)
cJSON_IsRaw(const cJSON * const item)
cJSON *item = cJSON_GetObjectItem(root, "age");
if (cJSON_IsNumber(item)) {
    printf("年龄:%g\n", item->valuedouble);
}

4.7 内存管理函数

cJSON_Delete()
void cJSON_Delete(cJSON *item);
  • 功能:释放cJSON对象及其所有子节点的内存
cJSON_Delete(root);  // 释放整个JSON树
cJSON_DetachItemFromArray()
cJSON* cJSON_DetachItemFromArray(cJSON *array, int which);
  • 功能:从数组中分离项(不释放内存)
cJSON *item = cJSON_DetachItemFromArray(array, 0);
// item现在可以被重新使用或单独删除
cJSON_Delete(item);
cJSON_DetachItemFromObject()
cJSON* cJSON_DetachItemFromObject(cJSON *object, const char *string);
  • 功能:从对象中分离项
cJSON *name_item = cJSON_DetachItemFromObject(root, "name");
if (name_item != NULL) {
    printf("分离的姓名:%s\n", name_item->valuestring);
    cJSON_Delete(name_item);
}

5. 实际应用示例

5.1 创建复杂JSON

#include <stdio.h>
#include <stdlib.h>
#include "cjson/cJSON.h"

int main() {
    // 创建根对象
    cJSON *root = cJSON_CreateObject();
    if (root == NULL) {
        printf("创建根对象失败\n");
        return -1;
    }

    // 添加基本属性
    cJSON_AddStringToObject(root, "name", "张三");
    cJSON_AddNumberToObject(root, "age", 25);
    cJSON_AddBoolToObject(root, "married", 0);
    cJSON_AddNullToObject(root, "spouse");

    // 创建地址对象
    cJSON *address = cJSON_CreateObject();
    cJSON_AddStringToObject(address, "street", "长安街1号");
    cJSON_AddStringToObject(address, "city", "北京");
    cJSON_AddStringToObject(address, "zipcode", "100000");
    cJSON_AddItemToObject(root, "address", address);

    // 创建电话数组
    cJSON *phones = cJSON_CreateArray();
    cJSON_AddItemToArray(phones, cJSON_CreateString("13800138000"));
    cJSON_AddItemToArray(phones, cJSON_CreateString("010-12345678"));
    cJSON_AddItemToObject(root, "phones", phones);

    // 创建技能数组
    cJSON *skills = cJSON_CreateArray();
    cJSON_AddItemToArray(skills, cJSON_CreateString("C语言"));
    cJSON_AddItemToArray(skills, cJSON_CreateString("Python"));
    cJSON_AddItemToArray(skills, cJSON_CreateString("Java"));
    cJSON_AddItemToObject(root, "skills", skills);

    // 打印JSON
    char *json_str = cJSON_Print(root);
    printf("生成的JSON:\n%s\n", json_str);

    // 释放内存
    free(json_str);
    cJSON_Delete(root);

    return 0;
}

5.2 解析JSON并提取数据

#include <stdio.h>
#include <stdlib.h>
#include "cjson/cJSON.h"

int main() {
    const char *json_str = 
        "{"
            "\"name\":\"张三\","
            "\"age\":25,"
            "\"married\":false,"
            "\"address\":{"
                "\"street\":\"长安街1号\","
                "\"city\":\"北京\","
                "\"zipcode\":\"100000\""
            "},"
            "\"phones\":[\"13800138000\",\"010-12345678\"],"
            "\"skills\":[\"C语言\",\"Python\",\"Java\"]"
        "}";

    // 解析JSON
    cJSON *root = cJSON_Parse(json_str);
    if (root == NULL) {
        printf("JSON解析失败\n");
        return -1;
    }

    // 提取基本属性
    cJSON *name = cJSON_GetObjectItem(root, "name");
    cJSON *age = cJSON_GetObjectItem(root, "age");
    cJSON *married = cJSON_GetObjectItem(root, "married");

    if (cJSON_IsString(name)) {
        printf("姓名:%s\n", name->valuestring);
    }

    if (cJSON_IsNumber(age)) {
        printf("年龄:%d\n", age->valueint);
    }

    if (cJSON_IsBool(married)) {
        printf("婚姻状况:%s\n", cJSON_IsTrue(married) ? "已婚" : "未婚");
    }

    // 提取地址对象
    cJSON *address = cJSON_GetObjectItem(root, "address");
    if (cJSON_IsObject(address)) {
        cJSON *street = cJSON_GetObjectItem(address, "street");
        cJSON *city = cJSON_GetObjectItem(address, "city");
        cJSON *zipcode = cJSON_GetObjectItem(address, "zipcode");

        if (cJSON_IsString(street)) {
            printf("街道:%s\n", street->valuestring);
        }
        if (cJSON_IsString(city)) {
            printf("城市:%s\n", city->valuestring);
        }
        if (cJSON_IsString(zipcode)) {
            printf("邮编:%s\n", zipcode->valuestring);
        }
    }

    // 提取电话数组
    cJSON *phones = cJSON_GetObjectItem(root, "phones");
    if (cJSON_IsArray(phones)) {
        int phone_count = cJSON_GetArraySize(phones);
        printf("电话数量:%d\n", phone_count);

        for (int i = 0; i < phone_count; i++) {
            cJSON *phone = cJSON_GetArrayItem(phones, i);
            if (cJSON_IsString(phone)) {
                printf("电话%d:%s\n", i + 1, phone->valuestring);
            }
        }
    }

    // 提取技能数组
    cJSON *skills = cJSON_GetObjectItem(root, "skills");
    if (cJSON_IsArray(skills)) {
        int skill_count = cJSON_GetArraySize(skills);
        printf("技能数量:%d\n", skill_count);

        for (int i = 0; i < skill_count; i++) {
            cJSON *skill = cJSON_GetArrayItem(skills, i);
            if (cJSON_IsString(skill)) {
                printf("技能%d:%s\n", i + 1, skill->valuestring);
            }
        }
    }

    // 释放内存
    cJSON_Delete(root);

    return 0;
}

5.3 JSON数组操作

#include <stdio.h>
#include <stdlib.h>
#include "cjson/cJSON.h"

int main() {
    // 创建数组
    cJSON *array = cJSON_CreateArray();
    if (array == NULL) {
        printf("创建数组失败\n");
        return -1;
    }

    // 添加不同类型的元素
    cJSON_AddItemToArray(array, cJSON_CreateString("苹果"));
    cJSON_AddItemToArray(array, cJSON_CreateString("香蕉"));
    cJSON_AddItemToArray(array, cJSON_CreateString("橙子"));

    cJSON_AddItemToArray(array, cJSON_CreateNumber(1));
    cJSON_AddItemToArray(array, cJSON_CreateNumber(2));
    cJSON_AddItemToArray(array, cJSON_CreateNumber(3));

    cJSON_AddItemToArray(array, cJSON_CreateTrue());
    cJSON_AddItemToArray(array, cJSON_CreateFalse());
    cJSON_AddItemToArray(array, cJSON_CreateNull());

    // 创建嵌套对象
    cJSON *obj1 = cJSON_CreateObject();
    cJSON_AddStringToObject(obj1, "name", "产品A");
    cJSON_AddNumberToObject(obj1, "price", 100.5);
    cJSON_AddItemToArray(array, obj1);

    cJSON *obj2 = cJSON_CreateObject();
    cJSON_AddStringToObject(obj2, "name", "产品B");
    cJSON_AddNumberToObject(obj2, "price", 200.8);
    cJSON_AddItemToArray(array, obj2);

    // 打印数组
    char *json_str = cJSON_Print(array);
    printf("数组内容:\n%s\n", json_str);

    // 遍历数组
    int size = cJSON_GetArraySize(array);
    printf("\n遍历数组内容:\n");
    for (int i = 0; i < size; i++) {
        cJSON *item = cJSON_GetArrayItem(array, i);
        printf("索引%d: ", i);

        if (cJSON_IsString(item)) {
            printf("字符串: %s\n", item->valuestring);
        } else if (cJSON_IsNumber(item)) {
            printf("数字: %g\n", item->valuedouble);
        } else if (cJSON_IsBool(item)) {
            printf("布尔值: %s\n", cJSON_IsTrue(item) ? "true" : "false");
        } else if (cJSON_IsNull(item)) {
            printf("null值\n");
        } else if (cJSON_IsObject(item)) {
            printf("对象: ");
            cJSON *name = cJSON_GetObjectItem(item, "name");
            cJSON *price = cJSON_GetObjectItem(item, "price");
            if (name && cJSON_IsString(name)) {
                printf("名称=%s, ", name->valuestring);
            }
            if (price && cJSON_IsNumber(price)) {
                printf("价格=%g", price->valuedouble);
            }
            printf("\n");
        }
    }

    // 释放内存
    free(json_str);
    cJSON_Delete(array);

    return 0;
}

5.4 错误处理和调试

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cjson/cJSON.h"

// 自定义错误处理函数
void print_json_error(const char *json_str, const char *error_msg) {
    printf("JSON错误:%s\n", error_msg);
    printf("JSON字符串:%s\n", json_str);
    
    // 找到错误位置(简化版本)
    const char *parse_end = NULL;
    cJSON *root = cJSON_ParseWithOpts(json_str, &parse_end, false);
    if (root == NULL && parse_end != NULL) {
        printf("错误位置:");
        for (int i = 0; i < strlen(json_str) && &json_str[i] != parse_end; i++) {
            if (json_str[i] == '\n') {
                printf("\\n");
            } else if (json_str[i] == '\t') {
                printf("\\t");
            } else {
                printf("%c", json_str[i]);
            }
        }
        printf(" <-- 这里\n");
    }
    if (root) cJSON_Delete(root);
}

int main() {
    // 测试正确的JSON
    const char *valid_json = "{\"name\":\"张三\",\"age\":25}";
    cJSON *root = cJSON_Parse(valid_json);
    if (root != NULL) {
        printf("正确JSON解析成功\n");
        cJSON_Delete(root);
    }

    // 测试错误的JSON
    const char *invalid_json = "{\"name\":\"张三\",\"age\":25";  // 缺少右括号
    root = cJSON_Parse(invalid_json);
    if (root == NULL) {
        print_json_error(invalid_json, "JSON格式错误");
    }

    // 更复杂的错误JSON
    const char *complex_invalid = 
        "{"
            "\"users\":["
                "{\"name\":\"张三\",\"age\":25},"
                "{\"name\":\"李四\",\"age\":\"二十五\"}"  // 年龄应该是数字
            "]"
        "}";
    
    root = cJSON_Parse(complex_invalid);
    if (root == NULL) {
        print_json_error(complex_invalid, "复杂JSON解析失败");
    } else {
        // 检查数据类型
        cJSON *users = cJSON_GetObjectItem(root, "users");
        if (cJSON_IsArray(users)) {
            cJSON *user = cJSON_GetArrayItem(users, 1);
            if (user) {
                cJSON *age = cJSON_GetObjectItem(user, "age");
                if (age && !cJSON_IsNumber(age)) {
                    printf("警告:年龄字段不是数字类型\n");
                }
            }
        }
        cJSON_Delete(root);
    }

    return 0;
}

6. 高级功能

6.1 JSON引用

// 创建引用,避免重复数据
cJSON *original = cJSON_CreateString("共享数据");
cJSON *reference = cJSON_CreateReference(original);

// 将引用添加到多个位置
cJSON *root1 = cJSON_CreateObject();
cJSON *root2 = cJSON_CreateObject();

cJSON_AddItemToObject(root1, "data", reference);
cJSON_AddItemToObject(root2, "data", cJSON_CreateReference(original));

// 注意:删除时要小心,避免重复释放
cJSON_Delete(root1);
cJSON_Delete(root2);
cJSON_Delete(original);  // 最后删除原始数据

6.2 自定义内存管理

// 设置自定义内存分配函数
void* custom_malloc(size_t size) {
    printf("分配内存:%zu字节\n", size);
    return malloc(size);
}

void custom_free(void *ptr) {
    printf("释放内存\n");
    free(ptr);
}

// 在解析前设置
cJSON_Hooks hooks;
hooks.malloc_fn = custom_malloc;
hooks.free_fn = custom_free;
cJSON_InitHooks(&hooks);

// 现在所有的内存分配都会使用自定义函数
cJSON *root = cJSON_CreateObject();
// ... 使用cJSON ...
cJSON_Delete(root);

6.3 流式解析

// 对于大JSON文件,可以使用流式解析
int stream_parse(const char *filename) {
    FILE *file = fopen(filename, "r");
    if (!file) {
        printf("无法打开文件:%s\n", filename);
        return -1;
    }

    // 分配缓冲区
    char buffer[4096];
    cJSON *root = NULL;
    const char *parse_end = NULL;

    while (fgets(buffer, sizeof(buffer), file)) {
        // 去除换行符
        buffer[strcspn(buffer, "\n")] = 0;

        // 解析当前行
        cJSON *partial = cJSON_ParseWithOpts(buffer, &parse_end, false);
        if (partial) {
            if (root == NULL) {
                root = partial;
            } else {
                // 合并到根对象(需要根据具体结构实现)
                // 这里只是一个示例
                cJSON_Delete(partial);
            }
        } else {
            printf("解析失败:%s\n", buffer);
            printf("错误位置:%s\n", parse_end ? parse_end : "未知");
        }
    }

    fclose(file);

    if (root) {
        // 处理解析结果
        char *json_str = cJSON_Print(root);
        printf("解析结果:%s\n", json_str);
        free(json_str);
        cJSON_Delete(root);
    }

    return 0;
}

7. 最佳实践

7.1 内存管理

  • 总是使用cJSON_Delete()释放内存
  • 确保每个cJSON_Create*都有对应的cJSON_Delete
  • 避免内存泄漏
// 正确的内存管理
cJSON *root = cJSON_CreateObject();
if (root) {
    // ... 使用JSON ...
    cJSON_Delete(root);  // 必须释放
}

7.2 错误处理

  • 总是检查函数返回值
  • 处理解析错误
  • 验证数据类型
cJSON *root = cJSON_Parse(json_str);
if (root == NULL) {
    printf("JSON解析失败\n");
    return -1;
}

cJSON *name = cJSON_GetObjectItem(root, "name");
if (name == NULL || !cJSON_IsString(name)) {
    printf("缺少或无效的name字段\n");
    cJSON_Delete(root);
    return -1;
}

7.3 性能优化

  • 对于频繁使用的JSON结构,考虑缓存解析结果
  • 使用cJSON_PrintUnformatted()减少输出大小
  • 避免不必要的JSON创建和解析

8. 常见问题解答

Q1: 如何处理中文字符?

A: cJSON支持UTF-8编码,确保你的JSON字符串是UTF-8编码即可。

Q2: 如何处理大文件?

A: 对于大JSON文件,考虑分块解析或使用流式处理。

Q3: 如何避免内存泄漏?

A: 确保每个创建的cJSON对象都有对应的删除操作,可以使用RAII模式或智能指针(在C++中)。

Q4: cJSON线程安全吗?

A: cJSON本身不是线程安全的,如果在多线程环境中使用,需要自行加锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值