cJSON-微型JSON引擎
cJSON:OpenHarmony 里"无处不在"的微型 JSON 引擎
导读:从开机配置、密钥管理到应用安装、权限控制,OpenHarmony 的各个组件之间交流时使用的"共同语言"就是 JSON。而在 C 语言的世界里,把这种"语言"翻译成程序能直接操作的数据结构,靠的却只是三两个源文件——这就是 cJSON,一个单文件即可落地的超轻量 JSON 解析器。
一、为什么系统内部到处是 JSON
一个操作系统内部有大量"结构化数据交换"的场景:初始化进程要读系统配置、包管理器要解析应用元数据、安全服务要核对权限策略、JS 框架要与原生侧传数据。这些数据用什么格式?JSON 几乎成了默认答案——它可读、可调试、跨语言、标准通用。
但在 C 语言里,字符串形式的 JSON 无法直接使用,必须先"解析"成内存中的数据结构,用完后还要"序列化"回字符串。这个反复出现的需求,催生了 cJSON 这样的基础库。
二、cJSON 是什么
cJSON 是 DaveGamble 用纯 ANSI C 编写的一个超轻量 JSON 解析器,采用 MIT 许可,OpenHarmony 引入的版本是 1.7.19。
整个库的核心就是两个源文件:
cJSON.c—— 实现(约 80KB)cJSON.h—— 头文件与 API 声明
外加一个 cJSON_Utils.c(提供 JSON 补丁、指针等扩展工具)。不依赖任何第三方库,理论上"拷贝即用",这在嵌入式领域是极大的加分项——接入成本几乎为零。
三、灵魂:一个结构体构建的 JSON 树
cJSON 的设计哲学可以用"数据结构极简主义"来形容。整个库的心脏,就是 cJSON.h 里的一个结构体:
typedef struct cJSON {
struct cJSON *next; /* 兄弟节点指针(数组元素 / 对象成员) */
struct cJSON *prev; /* 前一个兄弟节点 */
struct cJSON *child; /* 子节点指针(对象/数组的第一个元素) */
int type; /* 节点类型:对象/数组/字符串/数字/布尔/空 */
char *valuestring; /* 字符串值 */
int valueint; /* 整数值(历史字段) */
double valuedouble; /* 浮点值 */
char *string; /* 键名(对象成员时有效) */
} cJSON;
一个结构体 + 链表指针,就同时完成了"解析"和"生成"两种能力:
- 解析:递归读取 JSON 文本,按层级构建
child树,兄弟间用next/prev串成双向链表; - 生成:从树根向下遍历,输出成 JSON 字符串。
理解了这个结构,cJSON 的全部 API 就都"可推断"了——操作对象不外乎是"往树里加节点、从树里取节点、把树打成字符串"。
四、API 全景
4.1 解析(JSON 文本 → cJSON 树)
cJSON *cJSON_Parse(const char *value); /* 最常用 */
cJSON *cJSON_ParseWithOpts(const char *value,
const char **return_parse_end,
cJSON_bool require_null_terminated); /* 精细控制 */
cJSON *cJSON_ParseWithLength(const char *value, size_t buffer_length);
const char *cJSON_GetErrorPtr(void); /* 出错位置 */
cJSON_ParseWithOpts 可以拿到解析停止的位置,适合在较大的文本流里定位数据;require_null_terminated 则用于要求整段文本必须合法完整。
4.2 取值(按路径访问)
cJSON *cJSON_GetObjectItem(const cJSON *object, const char *string); /* 取对象成员 */
cJSON *cJSON_GetArrayItem(const cJSON *array, int index); /* 取数组元素 */
int cJSON_GetArraySize(const cJSON *array); /* 数组长度 */
const char *cJSON_GetStringValue(const cJSON *item); /* 取字符串值 */
double cJSON_GetNumberValue(const cJSON *item); /* 取数值 */
4.3 构造(程序 → JSON 文本)
cJSON *cJSON_CreateObject(void);
cJSON *cJSON_CreateArray(void);
cJSON *cJSON_CreateString(const char *string);
cJSON *cJSON_CreateNumber(double num);
void cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
void cJSON_AddItemToArray(cJSON *array, cJSON *item);
char *cJSON_Print(const cJSON *item); /* 格式化输出 */
char *cJSON_PrintUnformatted(const cJSON *item); /* 紧凑输出 */
void cJSON_Delete(cJSON *item); /* 释放整棵树 */
五、在 OpenHarmony 中的集成
5.1 双形态构建 + 统一配置
BUILD.gn 针对 lite 与 standard 两套构建体系分别产出两种形态:
cjson(动态库)cjson_static(静态库)
并为所有形态统一注入防御性配置:
config("cjson_config") {
include_dirs = [ "//third_party/cJSON" ]
ldflags = [ "-lm" ]
defines = [ "CJSON_NESTING_LIMIT=(128)" ]
}
两个细节值得展开:
**(1)-lm**:cJSON 处理数字时依赖 C 标准库的 math.h,因此需要链接数学库;
(2)CJSON_NESTING_LIMIT=(128):这是 cJSON 的递归深度限制。JSON 解析本质是递归下降,恶意构造一个几千层的嵌套 JSON,能让递归栈被打穿(栈溢出),这是嵌入式场景下解析外部数据最常见的攻击面。OpenHarmony 把它统一限制在 128 层,从编译层面堵死了这个隐患。
5.2 认证与部署
在 standard 系统下,cjson 动态库带有 chipsetsdk_sp、platformsdk_indirect 等接口标签,并部署到 system 与 updater 两个镜像。连"系统升级程序"解析配置都要用它,足以说明 cJSON 在系统里的基础地位。
5.3 部件声明
bundle.json 中部件 cJSON 属于 thirdparty 子系统,适配 mini / small / standard 三档系统,通过 inner_kits 对外提供 cjson 与 cjson_static 两个库,零组件依赖(deps.components 为空)——这也印证了它"轻到没有依赖"的定位。
六、谁在用?——从开机到安装的全链路
在 OpenHarmony 源码中检索依赖 cJSON 的组件,几乎覆盖了系统最核心的几条链路:
base/startup/init—— 开机时解析系统配置文件;base/security/huks—— 密钥管理中的策略与属性描述;base/security/appverify、base/security/permission_lite—— 应用签名校验、权限策略解析;foundation/bundlemanager/bundle_framework_lite、foundation/ability/appfwk_lite—— 轻量包管理、应用框架的元数据处理;foundation/arkui/ace_engine_lite—— JS 应用与原生侧的数据交换;developtools/syscap_codec—— 系统能力(syscap)编码。
一句话总结:配置文件是它解析的,应用元数据是它解析的,权限策略也是它解析的——cJSON 就是 OpenHarmony 内部信息交换的"事实标准"。
七、上手示例:解析与生成
#include <stdio.h>
#include <stdlib.h>
#include "cJSON.h"
int main(void)
{
/* ---- 解析 ---- */
const char *text = "{\"name\":\"player\",\"volume\":80,\"tags\":[\"audio\",\"video\"]}";
cJSON *root = cJSON_Parse(text);
if (root == NULL) {
printf("parse error at: %s\n", cJSON_GetErrorPtr());
return -1;
}
cJSON *name = cJSON_GetObjectItem(root, "name"); /* "player" */
cJSON *vol = cJSON_GetObjectItem(root, "volume"); /* 80 */
cJSON *tags = cJSON_GetObjectItem(root, "tags");
int tagCnt = cJSON_GetArraySize(tags);
printf("name=%s volume=%d tags=%d\n",
cJSON_GetStringValue(name),
(int)cJSON_GetNumberValue(vol),
tagCnt);
/* ---- 生成 ---- */
cJSON *out = cJSON_CreateObject();
cJSON_AddStringToObject(out, "state", "playing");
cJSON_AddNumberToObject(out, "volume", 80);
char *buf = cJSON_PrintUnformatted(out); /* {"state":"playing","volume":80} */
printf("%s\n", buf);
free(buf);
cJSON_Delete(out);
cJSON_Delete(root);
return 0;
}
要点:cJSON_Parse 返回的树用完必须 cJSON_Delete 释放;cJSON_PrintUnformatted 返回的字符串由 malloc 分配,需用 free 释放——谁申请谁释放,这是使用 cJSON 最容易踩的坑。
八、质量保障:完善的测试体系
cJSON 自带一套相当完整的单测,覆盖了解析与打印的方方面面:
tests/
├── parse_string.c / parse_array.c / parse_object.c / parse_number.c ...
├── print_string.c / print_array.c / print_object.c / print_value.c ...
├── compare_tests.c # 节点比较
├── minify_tests.c # 压缩空白
├── json_patch_tests.c # JSON Patch
├── parse_with_opts.c # 精细解析
└── unity/ # 测试框架
另外还有独立的 fuzzing/ 模糊测试目录。对嵌入式基础库来说,"解析器 + 模糊测试"的组合是安全性的重要保障——它们保证了解析边界情况下的行为可预期,也是 OpenHarmony 引入它时的重要依据。
九、总结
cJSON 用最朴素的代码哲学,解决了最普遍的需求:轻量、零依赖、够用。
在 OpenHarmony 里,它早已不只是"一个第三方库",而是贯穿启动、安全、应用框架的数据交换地基。它的意义可以总结为三点:
- 接入零成本——单文件、零依赖,任何组件都可以快速引入;
- 防御有保障——嵌套深度限制、内存管理约定清晰,配合 fuzzing 测试,可靠性有据可查;
- 生态最通用——上游成熟稳定,团队熟悉度高,维护成本低。
对开发者而言,读透这两千来行源码,基本就掌握了整个系统的"配置语言"。
相关源码位置:
third_party/cJSON(cJSON.c/cJSON.h/cJSON_Utils.c)
相关部件:cJSON(子系统thirdparty)
更多推荐
所有评论(0)