找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1561

积分

0

好友

231

主题
发表于 3 天前 | 查看: 2879| 回复: 0

对于C语言开发者而言,处理JSON格式数据常常是一个痛点。无论是调用RESTful API、解析配置文件,还是与其他系统进行数据交换,面对{"key": "value"}这样的结构,C语言标准库并未提供原生支持。手动解析字符串不仅繁琐易错,而且难以维护。

为此,cJSON库应运而生。它定位于成为“能帮你完成工作的、最简单的解析器”,凭借极简的设计哲学,成为C语言生态中广受欢迎的JSON库。

cJSON的核心优势

  • 超轻量级:整个库仅包含cJSON.ccJSON.h两个文件,无需复杂构建系统。
  • 高兼容性:采用ANSI C (C89)标准编写,兼容几乎所有编译环境和平台。
  • API直观:设计简单直接,核心功能聚焦于JSON的解析与生成。

极简集成:三步引入项目

与许多需要复杂配置的库不同,cJSON的集成过程异常简单:

  1. 从GitHub获取源码或直接复制cJSON.hcJSON.c文件。
  2. 将这两个文件放入您的项目目录。
  3. 在源代码中包含头文件:#include "cJSON.h"

如果文件位于子目录,如libs/cjson/,则包含路径相应修改即可。集成完毕,您可以立即开始使用。

实战一:动态创建JSON对象

在cJSON中,所有JSON元素(对象、数组、字符串、数字等)均由cJSON结构体表示,如同统一的“乐高积木”。

常用创建函数 JSON元素 创建函数
对象 {} cJSON_CreateObject()
数组 [] cJSON_CreateArray()
字符串 "text" cJSON_CreateString("text")
数字 123 cJSON_CreateNumber(123)
布尔值 true cJSON_CreateTrue()
空值 null cJSON_CreateNull()

示例:构建显示器信息JSON
假设需要生成如下JSON,描述显示器及其支持的分辨率:

{
    "name": "Awesome 4K",
    "resolutions": [
        {"width": 1280, "height": 720},
        {"width": 1920, "height": 1080},
        {"width": 3840, "height": 2160}
    ]
}

实现代码如下:

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

char* create_monitor_json(void) {
    // 1. 创建根对象
    cJSON *monitor = cJSON_CreateObject();

    // 2. 添加名称字段
    cJSON_AddStringToObject(monitor, "name", "Awesome 4K");

    // 3. 创建并添加分辨率数组
    cJSON *resolutions = cJSON_AddArrayToObject(monitor, "resolutions");

    // 4. 向数组中添加分辨率对象
    int res_data[3][2] = {{1280, 720}, {1920, 1080}, {3840, 2160}};
    for (int i = 0; i < 3; i++) {
        cJSON *res = cJSON_CreateObject();
        cJSON_AddNumberToObject(res, "width", res_data[i][0]);
        cJSON_AddNumberToObject(res, "height", res_data[i][1]);
        cJSON_AddItemToArray(resolutions, res); // 所有权转移
    }

    // 5. 将对象树转换为格式化字符串
    char *json_str = cJSON_Print(monitor);

    // 6. 释放cJSON对象树
    cJSON_Delete(monitor);

    return json_str; // 调用者需负责释放此字符串
}

int main(void) {
    char *result = create_monitor_json();
    printf("%s\n", result);
    free(result); // 释放cJSON_Print分配的字符串
    return 0;
}

对象构建流程
图片

字符串输出函数

  • cJSON_Print(): 生成格式化的、带缩进的字符串,便于阅读和调试。
  • cJSON_PrintUnformatted(): 生成紧凑型字符串,节省空间,适合网络传输。

重要提示cJSON_Print()系列函数在堆上分配内存,返回的字符串使用后必须调用free()释放,否则会导致内存泄漏。

实战二:解析JSON字符串

当接收到JSON字符串时,使用cJSON_Parse()进行解析。

安全解析
cJSON_Parse()在解析失败时会返回NULL,因此必须检查返回值。

cJSON *root = cJSON_Parse(json_string);
if (root == NULL) {
    const char *error = cJSON_GetErrorPtr();
    printf("JSON解析错误:%s\n", error);
    // 错误处理...
}

数据提取“三板斧”

  1. 安全访问成员:使用cJSON_GetObjectItemCaseSensitive(),该函数即使传入NULL对象也不会崩溃,支持安全链式调用。
    cJSON *city = cJSON_GetObjectItemCaseSensitive(
                    cJSON_GetObjectItemCaseSensitive(root, "address"),
                    "city");
  2. 类型检查:提取值后,应先判断其类型,这是防御性编程的良好实践。
    cJSON *name = cJSON_GetObjectItemCaseSensitive(root, "name");
    if (cJSON_IsString(name) && name->valuestring != NULL) {
        printf("名称: %s\n", name->valuestring);
    }
  3. 遍历数组:使用cJSON_ArrayForEach宏可以像普通循环一样遍历JSON数组。
    cJSON *res_item = NULL;
    cJSON *resolutions = cJSON_GetObjectItemCaseSensitive(root, "resolutions");
    cJSON_ArrayForEach(res_item, resolutions) {
        // 处理每个数组元素res_item
    }

完整示例:检查显示器是否支持1080P分辨率

#include <stdio.h>
#include "cJSON.h"

int supports_full_hd(const char *json_str) {
    cJSON *root = cJSON_Parse(json_str);
    if (root == NULL) {
        printf("JSON解析失败\n");
        return 0;
    }

    cJSON *name = cJSON_GetObjectItemCaseSensitive(root, "name");
    if (cJSON_IsString(name)) {
        printf("检查设备: %s\n", name->valuestring);
    }

    int found = 0;
    cJSON *resolutions = cJSON_GetObjectItemCaseSensitive(root, "resolutions");
    cJSON *res = NULL;
    cJSON_ArrayForEach(res, resolutions) {
        cJSON *w = cJSON_GetObjectItemCaseSensitive(res, "width");
        cJSON *h = cJSON_GetObjectItemCaseSensitive(res, "height");
        if (cJSON_IsNumber(w) && cJSON_IsNumber(h)) {
            if (w->valuedouble == 1920 && h->valuedouble == 1080) {
                found = 1;
                break;
            }
        }
    }
    cJSON_Delete(root); // 释放整个对象树
    return found;
}

关键:理解cJSON的内存管理规则

在C语言中使用动态内存分配库,明确内存所有权至关重要。

原则一:谁创建(或解析),谁释放
无论是通过cJSON_Parse()解析得到的,还是通过cJSON_CreateObject()等函数创建的顶层对象,在使用完毕后都必须调用cJSON_Delete()进行释放。该函数会递归释放对象及其所有子节点。

cJSON *root = cJSON_Parse(json_str);
// ... 使用root ...
cJSON_Delete(root); // 必须释放

原则二:所有权转移(避免重复释放)
当使用cJSON_AddItemToObject()cJSON_AddItemToArray()将一个子节点添加到父节点后,该子节点的内存管理责任就转移给了父节点。此后,只能通过释放父节点来间接释放子节点,切勿再单独释放子节点,否则会导致“重复释放”(double free)错误。

// 正确做法
cJSON *root = cJSON_CreateObject();
cJSON *child = cJSON_CreateString("hello");
cJSON_AddItemToObject(root, "msg", child); // child的所有权转移给root
// ... 使用 ...
cJSON_Delete(root); // 一次性释放root及其所有子项(包括child)

// 错误做法:导致重复释放和程序崩溃
// cJSON_Delete(child); // 错误!所有权已转移
// cJSON_Delete(root);  // 这里会再次尝试释放child

内存所有权转移示意图
图片

总结与核心API速查

cJSON如同C语言开发者的瑞士军刀,尤其适合在资源受限的嵌入式环境或追求极致轻量化的场景中使用。

黄金法则:牢记“Add之后勿Delete,顶层对象必须删”,可规避绝大多数内存问题。

核心API速查表 功能 函数
解析字符串 cJSON_Parse()
创建对象/数组 cJSON_CreateObject()/cJSON_CreateArray()
向对象添加成员 cJSON_AddStringToObject()
从对象获取成员 cJSON_GetObjectItemCaseSensitive()
遍历数组 cJSON_ArrayForEach
类型判断 cJSON_IsString(), cJSON_IsNumber()
生成字符串 cJSON_Print(), cJSON_PrintUnformatted()
释放内存 cJSON_Delete()

通过cJSON,C语言开发者可以轻松应对现代网络通信与数据交换中的JSON处理需求,告别手写解析器的时代。项目开源地址:https://github.com/DaveGamble/cJSON




上一篇:技术面试后HR的委婉拒绝:如何解读“感谢你的时间”等客套话术
下一篇:2026年React组件库选型指南:主流评测、性能对比与避坑指南
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 17:08 , Processed in 0.160683 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表