如何用C语言开发JSON应用?

99ANYc3cd6
预计阅读时长 38 分钟
位置: 首页 C语言 正文

目录

  1. 为什么在 C 语言中处理 JSON?
  2. 主流 C 语言 JSON 库对比
  3. 开发环境准备
  4. 实战演练:使用 cJSON
    • 1 解析 JSON 字符串
    • 2 遍历和访问 JSON 数据
    • 3 创建和生成 JSON
    • 4 修改和删除 JSON 元素
    • 5 释放内存
  5. 最佳实践与注意事项

为什么在 C 语言中处理 JSON?

C 语言作为一种系统级编程语言,以其高性能和底层控制能力著称,在以下场景中,你可能需要在 C 语言中处理 JSON:

json c 语言开发指南
(图片来源网络,侵删)
  • 嵌入式系统和物联网设备:资源受限的设备需要高效的 JSON 解析器来处理来自云平台或 API 的数据。
  • 高性能服务器后端:对于追求极致性能的服务器,用 C 语言直接处理 JSON 可以避免其他语言解释器的开销。
  • 系统工具和脚本:开发需要与 JSON 配置文件或网络 API 交互的命令行工具。
  • 与现有 C/C++ 代码库集成:当你的核心逻辑是用 C/C++ 编写的,而外部数据交换格式是 JSON 时。

主流 C 语言 JSON 库对比

选择一个合适的库是成功的关键,以下是几个最流行的库及其特点:

库名 特点 优点 缺点 适用场景
cJSON 单文件实现,轻量级,易于集成。 极易使用,API 简洁,文档和社区支持好,无依赖。 功能相对基础,性能不是顶尖。 初学者首选,嵌入式,快速原型开发。
JSMN 单文件实现,流式解析器,非常轻量。 极致轻量,内存占用极小,速度快,支持流式处理。 API 更底层,需要手动管理状态,功能比 cJSON 少。 内存极度受限的嵌入式系统。
Parson 单文件实现,轻量级,API 风格类似 cJSON 易于使用,API 设计友好,支持流式解析和生成。 社区和支持相比 cJSON 较小。 cJSON 的一个优秀替代品。
yajl (Yet Another JSON Library) C 库,也提供绑定(如 yajl-ruby)。 性能高,支持 SAX 风格(流式)和 DOM 风格解析。 依赖 CMake 构建,API 相对复杂。 对性能有高要求的服务端应用。
simdjson 现代库,利用 SIMD 指令实现极速解析。 目前最快的 JSON 解析器之一,性能惊人。 依赖较新的 CPU 指令集(如 SSE4.2, AVX),API 与传统 DOM 模型不同。 需要处理海量 JSON 数据的高性能计算场景。

推荐:对于大多数开发者,cJSON 是最佳入门和通用选择,它简单、强大且足够快,本指南将以 cJSON 为例进行详细讲解。


开发环境准备

以 Linux (Ubuntu/Debian) 为例,Windows 用户可以使用 MinGW 或 MSYS2。

1 获取 cJSON 源码

cJSON 是单文件库,非常方便。

json c 语言开发指南
(图片来源网络,侵删)
# 克隆 cJSON 的 GitHub 仓库
git clone https://github.com/DaveGamble/cJSON.git
cd cJSON

2 编译 cJSON

cJSON 提供了一个 Makefile,可以方便地编译成一个静态库(.a 文件)。

# 编译生成 libcjson.a
make

编译成功后,你会看到 libcjson.acJSON.h 文件。cJSON.h 是头文件,libcjson.a 是编译好的库文件。

3 项目结构

为了方便管理,建议你的项目结构如下:

my_json_project/
├── include/
│   └── cJSON.h      # 从 cJSON 项目中复制过来
├── lib/
│   └── libcjson.a   # 从 cJSON 项目中复制过来
└── src/
    └── main.c       # 你的 C 源代码

实战演练:使用 cJSON

我们将创建一个 main.c 文件,并编写代码来解析和生成 JSON。

json c 语言开发指南
(图片来源网络,侵删)

1 解析 JSON 字符串

我们来看如何解析一个 JSON 字符串。

src/main.c

#include <stdio.h>
#include <stdlib.h>
#include "include/cJSON.h" // 包含头文件
int main() {
    // 1. 准备一个 JSON 字符串
    const char *json_string = "{"
        "\"name\": \"John Doe\","
        "\"age\": 30,"
        "\"is_student\": false,"
        "\"courses\": [\"Math\", \"Physics\", \"Computer Science\"],"
        "\"address\": {"
            "\"street\": \"123 Main St\","
            "\"city\": \"New York\""
        "}"
    "}";
    // 2. 解析 JSON 字符串
    // cJSON_Parse 会分配内存,返回一个 cJSON 对象指针
    cJSON *root = cJSON_Parse(json_string);
    // 3. 检查解析是否成功
    if (root == NULL) {
        const char *error_ptr = cJSON_GetErrorPtr();
        if (error_ptr != NULL) {
            fprintf(stderr, "Error before: %s\n", error_ptr);
        }
        return 1;
    }
    printf("JSON parsing successful!\n");
    // ... 在这里进行数据访问 ...
    // 4. 释放内存 (非常重要!)
    cJSON_Delete(root);
    return 0;
}

2 遍历和访问 JSON 数据

解析成功后,我们可以通过 cJSON 提供的函数来访问数据。

main.c 中添加以下代码(在 printf 之后,cJSON_Delete 之前):

// 访问字符串 "name"
cJSON *name_item = cJSON_GetObjectItemCaseSensitive(root, "name");
if (cJSON_IsString(name_item) && (name_item->valuestring != NULL)) {
    printf("Name: %s\n", name_item->valuestring);
}
// 访问数字 "age"
cJSON *age_item = cJSON_GetObjectItemCaseSensitive(root, "age");
if (cJSON_IsNumber(age_item)) {
    printf("Age: %d\n", age_item->valueint); // valueint 用于获取整数值
    // printf("Age (as double): %f\n", age_item->valuedouble); // valuedouble 用于获取浮点数值
}
// 访问布尔值 "is_student"
cJSON *is_student_item = cJSON_GetObjectItemCaseSensitive(root, "is_student");
if (cJSON_IsBool(is_student_item)) {
    printf("Is Student: %s\n", cJSON_IsTrue(is_student_item) ? "true" : "false");
}
// 访问数组 "courses"
cJSON *courses_item = cJSON_GetObjectItemCaseSensitive(root, "courses");
if (cJSON_IsArray(courses_item)) {
    printf("Courses: [");
    cJSON *course = NULL;
    cJSON_ArrayForEach(course, courses_item) {
        if (cJSON_IsString(course)) {
            printf("\"%s\"", course->valuestring);
            if (course->next) { // 如果不是最后一个元素,打印逗号
                printf(", ");
            }
        }
    }
    printf("]\n");
}
// 访问嵌套对象 "address"
cJSON *address_item = cJSON_GetObjectItemCaseSensitive(root, "address");
if (cJSON_IsObject(address_item)) {
    cJSON *street_item = cJSON_GetObjectItemCaseSensitive(address_item, "street");
    cJSON *city_item = cJSON_GetObjectItemCaseSensitive(address_item, "city");
    if (cJSON_IsString(street_item) && cJSON_IsString(city_item)) {
        printf("Address: %s, %s\n", street_item->valuestring, city_item->valuestring);
    }
}

编译并运行:

# -I 指定头文件路径,-L 指定库文件路径,-lcjson 链接库
gcc src/main.c -I./include -L./lib -lcjson -o my_json_app
# 运行 (可能需要设置 LD_LIBRARY_PATH 以便找到库文件)
./my_json_app

预期输出:

JSON parsing successful!
Name: John Doe
Age: 30
Is Student: false
Courses: ["Math", "Physics", "Computer Science"]
Address: 123 Main St, New York

3 创建和生成 JSON

cJSON 不仅可以解析,还可以从零开始构建 JSON。

main.c 中添加一个新的 main 函数或替换现有逻辑:

#include <stdio.h>
#include <stdlib.h>
#include "include/cJSON.h"
int main_create() {
    // 1. 创建根对象 (一个 JSON 对象)
    cJSON *root = cJSON_CreateObject();
    // 2. 向对象中添加键值对
    cJSON_AddStringToObject(root, "name", "Jane Doe");
    cJSON_AddNumberToObject(root, "age", 28);
    cJSON_AddBoolToObject(root, "is_student", cJSON_False); // 或 cJSON_True
    // 3. 创建并填充数组
    cJSON *courses = cJSON_CreateArray();
    cJSON_AddItemToArray(courses, cJSON_CreateString("History"));
    cJSON_AddItemToArray(courses, cJSON_CreateString("Literature"));
    cJSON_AddItemToObject(root, "courses", courses); // 将数组添加到根对象
    // 4. 创建嵌套对象
    cJSON *address = cJSON_CreateObject();
    cJSON_AddStringToObject(address, "street", "456 Oak Ave");
    cJSON_AddStringToObject(address, "city", "Boston");
    cJSON_AddItemToObject(root, "address", address);
    // 5. 将 cJSON 对象转换为格式化的 JSON 字符串
    // cJSON_Print 会进行美化,换行和缩进
    char *json_formatted_string = cJSON_Print(root);
    printf("Generated JSON (pretty-printed):\n%s\n", json_formatted_string);
    // 6. 将 cJSON 对象转换为未格式化的 JSON 字符串 (更紧凑)
    // cJSON_PrintUnformatted 不会添加多余的空格和换行
    char *json_compact_string = cJSON_PrintUnformatted(root);
    printf("Generated JSON (compact):\n%s\n", json_compact_string);
    // 7. 释放字符串的内存 (cJSON_Print 分配了内存)
    free(json_formatted_string);
    free(json_compact_string);
    // 8. 释放 cJSON 对象的内存
    cJSON_Delete(root);
    return 0;
}

编译并运行:

gcc src/main.c -I./include -L./lib -lcjson -o my_json_app
./my_json_app

预期输出:

Generated JSON (pretty-printed):
{
    "name": "Jane Doe",
    "age":  28,
    "is_student":   false,
    "courses":  [
        "History",
        "Literature"
    ],
    "address":  {
        "street":   "456 Oak Ave",
        "city": "Boston"
    }
}
Generated JSON (compact):
{"name":"Jane Doe","age":28,"is_student":false,"courses":["History","Literature"],"address":{"street":"456 Oak Ave","city":"Boston"}}

4 修改和删除 JSON 元素

修改和删除是解析和创建的结合。

// 假设我们已经有一个解析好的 root 对象 (从 4.1 节的 json_string 解析而来)
// ...
// 修改
cJSON *age_item = cJSON_GetObjectItemCaseSensitive(root, "age");
if (age_item) {
    age_item->valueint = 31; // 修改整数值
    age_item->valuedouble = 31.0; // 同时修改浮点数值
}
// 删除一个元素
cJSON_DeleteItemFromObjectCaseSensitive(root, "is_student");
// 在数组中删除一个元素
cJSON *courses_item = cJSON_GetObjectItemCaseSensitive(root, "courses");
if (courses_item && cJSON_IsArray(courses_item)) {
    // 找到要删除的元素 ("Physics")
    cJSON *course_to_delete = NULL;
    cJSON_ArrayForEach(course_to_delete, courses_item) {
        if (course_to_delete && cJSON_IsString(course_to_delete) && strcmp(course_to_delete->valuestring, "Physics") == 0) {
            cJSON_DeleteItemFromArray(courses_item, course_to_delete); // 注意:这里需要传递索引,但 API 设计如此,通常需要先找到索引
            break;
        }
    }
    // 更简单的方法是遍历并删除匹配的项
    int i;
    for (i = cJSON_GetArraySize(courses_item) - 1; i >= 0; i--) {
        cJSON *item = cJSON_GetArrayItem(courses_item, i);
        if (item && cJSON_IsString(item) && strcmp(item->valuestring, "Physics") == 0) {
            cJSON_DeleteItemFromArray(courses_item, i);
            break;
        }
    }
}
// 生成并打印修改后的 JSON
char *modified_json = cJSON_Print(root);
printf("Modified JSON:\n%s\n", modified_json);
free(modified_json);
// ...

5 释放内存

这是最重要的一点! cJSON 的所有解析和创建函数都会在堆上分配内存,你必须手动释放这些内存,否则会导致内存泄漏。

  • *`cJSON_Delete(cJSON item)**: 递归删除一个cJSON` 对象及其所有子项,这是最常用的释放函数。
  • *`free(char string)cJSON_PrintcJSON_PrintUnformatted返回的字符串需要用free` 释放。

最佳实践与注意事项

  1. 始终检查返回值cJSON_Parse 可能返回 NULLcJSON_GetObjectItem 可能返回 NULL,在访问返回的指针之前,务必检查它是否为空。
  2. 使用 CaseSensitive 版本cJSON_GetObjectItemCaseSensitive 是更安全的选择,因为它可以避免因大小写不匹配导致的 bug,普通版本 cJSON_GetObjectItem 是大小写不敏感的。
  3. 善用类型检查宏:在访问数据之前,使用 cJSON_IsString, cJSON_IsNumber, cJSON_IsArray 等宏来验证数据类型,防止程序崩溃。
  4. 错误处理cJSON_GetErrorPtr() 可以在解析失败时提供出错的字符位置,对调试非常有帮助。
  5. 内存管理:遵循“谁分配,谁释放”的原则,解析的 cJSON 树用 cJSON_Delete 释放,cJSON_Print 产生的字符串用 free 释放。
  6. 线程安全cJSON 本身不是线程安全的,如果你在多线程环境中使用,必须为每个线程创建独立的 cJSON 对象,或者在使用 cJSON 的函数时加锁。

本指南带你从零开始,学习了在 C 语言中使用 cJSON 库进行 JSON 开发的全过程。

  • 选择库cJSON 因其简单易用成为入门首选。
  • 解析:使用 cJSON_Parse 将字符串解析为 cJSON 对象树,然后用 cJSON_GetObjectItemcJSON_GetArrayItem 遍历访问。
  • 生成:使用 cJSON_CreateObject/cJSON_CreateArray 创建结构,用 cJSON_Add...ToObject 添加数据,最后用 cJSON_Print 生成字符串。
  • 核心内存管理是 C 语言开发的重中之重,务必记得 cJSON_Deletefree

掌握了这些基本操作后,你就可以在 C 语言项目中自如地处理 JSON 数据了,随着需求的增长,你还可以探索 cJSON 更高级的功能,如自定义内存分配器、处理二进制数据等。

-- 展开阅读全文 --
头像
dede文件夹改名后空白,如何解决?
« 上一篇 今天
为何不自动获取?
下一篇 » 今天

相关文章

取消
微信二维码
支付宝二维码

目录[+]