3. cJSON库入门:移植、核心API与第一个JSON对象
好,咱们进入正题。JSON在嵌入式世界里,说白了就是数据交换的“通用语言”。ESP32作为物联网节点,免不了要和服务器、手机App打交道。那数据怎么组织?怎么解析?cJSON库就是干这个的。
我个人习惯,在项目初期就把cJSON库移植好。别等到后面数据收发出问题了,再回头找原因。那时候你想想看,排查起来多麻烦。
3.1 cJSON库的移植
cJSON库是纯C写的,就两个文件:cJSON.c 和 cJSON.h。移植到ESP32上,其实就三步。
第一步:获取源码
去GitHub上搜一下cJSON,把最新版下载下来。我建议用v1.7.15之后的版本,API更稳定。我之前用过老版本,有个内存泄漏的坑,折腾了我一整天。
第二步:添加到工程
在ESP-IDF工程里,把这两个文件放到 components 目录下。或者直接丢到 main 文件夹里,简单粗暴。我个人喜欢建一个 components/cjson 文件夹,结构更清晰。
第三步:配置CMakeLists.txt
在 CMakeLists.txt 里加上:
set(COMPONENT_SRCS "cJSON.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()
嗯,这里要注意:ESP-IDF默认开启了 newlib 的浮点支持,cJSON的浮点数打印功能才能正常工作。如果你用的是其他RTOS,记得检查一下。
3.2 cJSON核心API介绍
cJSON的API不多,但每个都很关键。我按使用频率给你排个序。
| API函数 | 功能说明 | 使用频率 |
|---|---|---|
cJSON_CreateObject() |
创建一个JSON对象(根节点) | ★★★★★ |
cJSON_CreateString() |
创建字符串类型节点 | ★★★★★ |
cJSON_CreateNumber() |
创建数字类型节点 | ★★★★★ |
cJSON_AddItemToObject() |
向对象中添加键值对 | ★★★★★ |
cJSON_Print() |
将JSON对象转为字符串 | ★★★★☆ |
cJSON_Parse() |
将字符串解析为JSON对象 | ★★★★☆ |
cJSON_Delete() |
释放JSON对象内存 | ★★★★★ |
为什么 cJSON_Delete() 使用频率也是五星?因为你不释放内存,ESP32那点RAM很快就爆了。我曾经在项目里漏了一次 cJSON_Delete(),结果设备运行48小时后,内存泄漏导致系统崩溃。从那以后,我写cJSON代码第一件事就是先写释放逻辑。
3.3 创建第一个JSON对象
光说不练假把式。咱们直接上手,创建一个传感器数据JSON。
假设我们要封装这样的数据:
{
"device": "ESP32_01",
"temperature": 25.6,
"humidity": 68.2,
"online": true
}
代码怎么写?看这里:
#include "cJSON.h"
void create_sensor_json(void)
{
// 1. 创建根对象
cJSON *root = cJSON_CreateObject();
if (root == NULL) {
// 内存分配失败,直接返回
return;
}
// 2. 添加键值对
cJSON_AddStringToObject(root, "device", "ESP32_01");
cJSON_AddNumberToObject(root, "temperature", 25.6);
cJSON_AddNumberToObject(root, "humidity", 68.2);
cJSON_AddBoolToObject(root, "online", 1); // true
// 3. 转为字符串
char *json_str = cJSON_Print(root);
if (json_str) {
printf("生成的JSON: %s\n", json_str);
free(json_str); // 注意:cJSON_Print分配的内存需要手动释放
}
// 4. 释放cJSON对象
cJSON_Delete(root);
}
cJSON_Print() 返回的字符串是动态分配的,用完一定要 free()。而 cJSON_Delete() 会递归释放所有子节点,你只需要释放根节点就行。
你可能会问:为什么不用 cJSON_AddItemToObject() ?其实上面的 cJSON_AddStringToObject() 就是它的封装。我个人习惯,简单类型用封装函数,复杂嵌套才用 cJSON_AddItemToObject()。
3.4 避坑指南
我刚开始用cJSON时,踩过不少坑。分享几个典型的:
- 内存泄漏:每次
cJSON_Parse()后,记得cJSON_Delete()。我曾经在循环里解析JSON,忘了释放,结果RAM被吃光。 - 空指针检查:cJSON的API在内存不足时会返回NULL。别偷懒,每次创建都检查一下。
- 浮点数精度:ESP32的浮点运算没问题,但打印时默认保留6位小数。如果你要控制精度,可以用
cJSON_SetNumberValue()配合cJSON_PrintPreallocated()。
好了,cJSON的入门就到这里。你把这个例子跑通,就算入门了。下一节咱们聊聊怎么解析JSON,那才是真正考验功底的地方。