C/C++ 开源JSON库详解:cJSON、json-c、jsoncpp
一、概述
JSON(JavaScript Object Notation)是现代软件开发中最通用的数据交换格式之一。在C/C++生态中,有三个非常成熟且广泛使用的开源JSON库:cJSON、json-c 和 jsoncpp。它们分别面向不同的应用场景,各有特色。
| 库名 | 开发语言 | 核心定位 | 典型应用场景 |
|---|---|---|---|
| cJSON | C (ANSI C) | 极轻量、单文件集成 | 嵌入式系统、物联网设备、RTOS |
| json-c | C | 引用计数、稳定可靠 | Linux系统组件、长期维护的C项目 |
| jsoncpp | C++ | 现代C++风格、开发友好 | C++应用程序、后台服务、客户端软件 |
二、各库详细介绍
1. cJSON —— 嵌入式开发的黄金标准
背景与定位
cJSON由Dave Gamble开发,是目前GitHub上star数最多的C语言JSON库之一。它被广泛认为是嵌入式/物联网领域的事实标准,据称超过90%的物联网设备相关项目都在使用它。
核心优势
① 极致的轻量性
- 整个库仅由两个文件组成:
cJSON.c和cJSON.h - 编译后二进制体积极小(通常几十KB)
- 可以轻松地直接拖入任何C/C++项目,无需复杂的构建配置
② 极佳的移植性
- 完全遵循ANSI C (C89)标准
- 不依赖任何第三方库
- 可以在MCU、RTOS、甚至内核态等极度受限的环境中运行
③ 简单直观的API设计
- API函数数量少,学习成本低
- 核心操作只有:Create、Add、Print、Parse、Delete
数据结构原理
cJSON使用一个巧妙的结构体来同时表示JSON节点的类型、值和层级关系:
typedef struct cJSON {
struct cJSON *next, *prev; // 同级节点用双向链表连接
struct cJSON *child; // 子节点指针(构成树状结构)
int type; // 节点类型标识
char *valuestring; // 字符串类型的值
double valuedouble; // 数值类型的值
char *string; // 当前节点的键名
} cJSON;
理解这个结构是掌握cJSON的关键:它本质上是用链表+树的方式在内存中组织JSON数据。
使用示例
#include "cJSON.h"
// 创建对象:{"name":"张三","age":25}
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "name", "张三");
cJSON_AddNumberToObject(root, "age", 25);
// 序列化为字符串
char *json_str = cJSON_Print(root);
printf("%s\n", json_str);
// 解析JSON
cJSON *parsed = cJSON_Parse(json_str);
cJSON *name = cJSON_GetObjectItem(parsed, "name");
printf("name: %s\n", name->valuestring);
// 释放内存(非常重要!)
cJSON_Delete(root);
cJSON_Delete(parsed);
free(json_str);
局限性
- 手动内存管理:必须记得调用
cJSON_Delete()和free(),否则会内存泄漏 - 非线程安全:多线程环境下需要自行加锁
- 性能中等:不是最快的库,但足以满足绝大多数嵌入式场景
适用场景总结
| ✅ 适合 | ❌ 不适合 |
|---|---|
| 单片机/嵌入式系统 | 追求极致性能的高并发服务 |
| 物联网设备 | 大型C++项目(推荐用jsoncpp) |
| 资源受限环境 | 需要线程安全的多线程程序 |
| 需要极简集成的项目 |
2. json-c —— C语言世界的稳定老兵
背景与定位
json-c是一个历史悠久的C语言JSON库,被广泛集成在各大Linux发行版的官方软件源中(可通过apt-get install libjson-c-dev直接安装)。它的设计目标是成为Linux/Unix系统级应用中稳定、可靠的JSON处理方案。
核心优势
① 引用计数内存管理
这是json-c最核心、最独特的设计。所有JSON对象都被封装在json_object结构体中,通过引用计数来自动管理生命周期:
json_object_get(obj)→ 引用计数+1json_object_put(obj)→ 引用计数-1,减到0时自动释放
这套机制在复杂的C项目中能有效减少内存泄漏的风险。
② 极高的稳定性
- API经过了十几年的考验,变化极小
- 如果你的项目需要维护5年、10年甚至更久,json-c是非常可靠的选择
- 被大量Linux系统组件(如网络管理器、系统服务等)所采用
③ 功能完整
- 支持JSON的完整数据类型
- 提供格式化输出(带缩进)和紧凑输出
- 支持从文件直接解析
使用示例
#include <json-c/json.h>
// 创建对象
struct json_object *root = json_object_new_object();
// 添加字段
json_object_object_add(root, "name", json_object_new_string("张三"));
json_object_object_add(root, "age", json_object_new_int(25));
// 添加数组
struct json_object *hobbies = json_object_new_array();
json_object_array_add(hobbies, json_object_new_string("reading"));
json_object_array_add(hobbies, json_object_new_string("swimming"));
json_object_object_add(root, "hobbies", hobbies);
// 打印(格式化输出)
printf("%s\n", json_object_to_json_string_ext(root, JSON_C_TO_STRING_PRETTY));
// 释放内存(引用计数减1,减到0时自动释放)
json_object_put(root);
局限性
- 编译配置稍复杂:需要编译安装或链接库文件
- API略显冗长:函数名较长,嵌套调用时代码会比较多
- 不是最轻量的:二进制体积比cJSON大
适用场景总结
| ✅ 适合 | ❌ 不适合 |
|---|---|
| Linux/Unix系统级C程序 | 嵌入式/MCU环境 |
| 需要长期维护的C项目 | 追求极致轻量的场景 |
| 希望有自动内存管理的C项目 | 小型临时工具(太重了) |
| 通过系统包管理器安装的场景 |
3. jsoncpp —— C++开发者的首选
背景与定位
jsoncpp是最受欢迎的C++ JSON库之一,由多个贡献者共同维护。它的设计理念是让C++开发者能用最自然的方式操作JSON,充分利用C++的语言特性来提供优雅的开发体验。
核心优势
① 符合直觉的运算符重载
这是jsoncpp最吸引人的地方。你可以像操作C++原生容器一样使用[]操作符:
Json::Value root; root["name"] = "张三"; // 自动创建键值对 root["age"] = 25; // 自动推断类型 root["hobbies"][0] = "reading"; // 数组自动创建 std::string name = root["name"].asString(); // 类型转换
几乎不需要学习专门的API,C++开发者上手极快。
② 完全自动的内存管理
遵循C++的RAII(资源获取即初始化)原则:
- 所有的
Json::Value对象都像std::string一样自动管理内存 - 离开作用域时自动析构
- 开发者无需手动调用任何释放函数,彻底杜绝内存泄漏
③ 丰富的输出格式
// 格式化输出(带缩进,适合人阅读) std::cout << root.toStyledString() << std::endl; // 紧凑输出(适合网络传输) Json::FastWriter writer; std::string compact = writer.write(root); // 流式输出(最灵活) Json::StreamWriterBuilder builder; builder["indentation"] = " "; // 自定义缩进 std::string custom = Json::writeString(builder, root);
④ 完善的错误处理
Json::CharReaderBuilder builder;
JSONCPP_STRING errs;
if (!Json::parseFromStream(builder, input_stream, &root, &errs)) {
// errs 包含详细的错误信息(行号、错误类型等)
std::cerr << "JSON解析失败: " << errs << std::endl;
}
⑤ 良好的中文支持
Json::StreamWriterBuilder builder;
builder["emitUTF8"] = true; // 输出原始UTF-8中文,不转义为\uXXXX
std::string json_with_chinese = Json::writeString(builder, root);
// 输出: {"name":"张三"} 而不是 {"name":"\u5F20\u4E09"}
使用示例
#include <json/json.h>
#include <iostream>
#include <fstream>
int main() {
// 构建JSON
Json::Value root;
root["name"] = "张三";
root["age"] = 25;
root["score"] = 98.5;
root["is_student"] = true;
// 添加数组
root["hobbies"].append("reading");
root["hobbies"].append("coding");
// 添加嵌套对象
root["address"]["city"] = "北京";
root["address"]["street"] = "朝阳路";
// 序列化输出
std::cout << "格式化输出:\n" << root.toStyledString() << std::endl;
// 保存到文件
std::ofstream file("output.json");
file << root;
file.close();
// 从文件解析
Json::Value parsed;
std::ifstream input("output.json");
input >> parsed;
std::cout << "读取name: " << parsed["name"].asString() << std::endl;
return 0; // 自动释放所有内存
}
局限性
- 性能不是最快的:比RapidJSON等专门优化的库稍慢
- 二进制体积较大:比cJSON大不少
- 需要C++环境:纯C项目无法使用
适用场景总结
| ✅ 适合 | ❌ 不适合 |
|---|---|
| 任何C++项目 | 纯C语言项目 |
| 追求开发效率的应用 | 资源极度受限的嵌入式设备 |
| 需要快速原型开发 | 追求极致性能的场景 |
| 后台服务/客户端软件 |
三、三者对比总结表
| 对比维度 | cJSON | json-c | jsoncpp |
|---|---|---|---|
| 语言 | C (ANSI C89) | C | C++ |
| 内存管理 | 手动 (Create/Delete) | 引用计数 (get/put) | 自动 (RAII) |
| 文件数量 | 2个 | 多个 | 多个 |
| 集成难度 | ⭐ 极低(复制即用) | ⭐⭐⭐ 中等 | ⭐⭐⭐ 中等 |
| 二进制体积 | ⭐ 很小 | ⭐⭐ 中等 | ⭐⭐⭐ 较大 |
| 开发效率 | ⭐⭐ 一般 | ⭐⭐ 一般 | ⭐⭐⭐ 很高 |
| 内存安全性 | ⭐⭐ 需注意 | ⭐⭐⭐ 较好 | ⭐⭐⭐ 很好 |
| 线程安全性 | 非安全 | 需注意 | 需注意 |
| 学习成本 | ⭐⭐ 较低 | ⭐⭐⭐ 中等 | ⭐ 最低(C++者) |
| 社区活跃度 | ⭐⭐⭐ 很高 | ⭐⭐ 较高 | ⭐⭐⭐ 很高 |
四、一句话速记
C++ 开发:用 jsoncpp,省心高效
嵌入式C:用 cJSON,轻量极致
Linux C:用 json-c,稳定可靠
五、补充说明:其他高性能选项
如果项目对性能有极致要求(如高频交易、实时数据处理),或者对现代C++特性(如C++11/14/17)有更高期待,以下两个库也值得了解:
| 库名 | 语言 | 特点 |
|---|---|---|
| RapidJSON | C++ | 腾讯出品,高性能,Header-only,功能最全面 |
| yyjson | C | 号称最快的C语言JSON库,单文件,读写达GB/s级别 |
但在绝大多数常规应用中,前面详细介绍的三个库已经足够胜任。