4. cJSON创建数据:核心函数详解
好,咱们进入正题。上一章我们聊了cJSON的基本结构,今天来手把手教你如何用cJSON创建数据。说白了,就是怎么把C语言里的变量,变成一串JSON字符串。
我个人习惯把cJSON的创建过程分成三步:建对象 → 塞数据 → 取字符串。今天重点讲前两步,也就是cJSON_CreateObject、cJSON_AddItemToObject,以及那堆AddNumber、AddString之类的函数。
4.1 cJSON_CreateObject:一切从这里开始
这个函数,就是创建一个空的JSON对象。你可以把它想象成一张白纸,等着你往上写东西。
cJSON *root = cJSON_CreateObject();
if (root == NULL) {
// 内存分配失败,处理错误
return;
}
嗯,这里要注意:cJSON_CreateObject会动态分配内存。我在项目里见过有人忘了检查返回值,结果在内存紧张的ESP32上直接崩了。所以,每次创建都要检查NULL,这是基本功。
4.2 cJSON_AddItemToObject:往对象里塞东西
有了空对象,接下来就是往里加键值对。cJSON_AddItemToObject就是干这个的。
cJSON_AddItemToObject(root, "name", cJSON_CreateString("ESP32"));
cJSON_AddItemToObject(root, "temp", cJSON_CreateNumber(25.6));
你看,每次调用都要先创建一个cJSON节点(比如cJSON_CreateString),然后通过AddItem挂到root下面。这就像组装乐高,先造小零件,再拼到大件上。
不过,我建议你直接用那些封装好的Add函数,比如cJSON_AddNumberToObject。为什么?因为少写一行代码,少一个出错的机会。我曾经在项目里手写AddItem,结果忘了CreateNumber,传了个NULL进去,调试了一下午才找到问题。
4.3 cJSON_AddNumberToObject:加数字,最常用
这个函数我用的最多。温度、湿度、电压、电流……全是数字。
cJSON_AddNumberToObject(root, "temperature", 25.6);
cJSON_AddNumberToObject(root, "humidity", 60.2);
cJSON_AddNumberToObject(root, "battery", 3.7);
注意,cJSON内部用double存储数字。所以如果你传一个int,它也会被转成double。这有什么影响?
举个例子:
cJSON_AddNumberToObject(root, "count", 100);
// 输出时可能是 "count": 100.0 而不是 "count": 100
嗯,有些场景下你希望输出整数,不带小数点。怎么办?我个人的做法是用cJSON_AddRawToObject,或者自己格式化字符串。不过大多数物联网场景,小数点无所谓,后端解析时都会处理。
4.4 cJSON_AddStringToObject:加字符串,别忘转义
字符串在JSON里必须用双引号括起来。cJSON会自动处理,你只管传char*就行。
cJSON_AddStringToObject(root, "device_id", "ESP32-001");
cJSON_AddStringToObject(root, "status", "online");
这里有个坑:如果字符串里包含双引号或反斜杠,cJSON会自动转义。但如果你自己拼接字符串,忘了转义,那生成的JSON就是非法的。
我曾经在项目里遇到过:设备上报的WiFi密码里有个反斜杠,结果JSON解析失败,设备一直连不上云。后来发现是字符串没转义。从那以后,我所有字符串都走cJSON的Add函数,绝不手动拼接。
4.5 cJSON_AddBoolToObject 和 cJSON_AddNullToObject
这两个用得少,但偶尔会用到。
cJSON_AddBoolToObject(root, "is_online", 1); // true
cJSON_AddBoolToObject(root, "is_online", 0); // false
cJSON_AddNullToObject(root, "error");
注意:cJSON里,非零值就是true,零就是false。但输出时,1会变成true,0会变成false。这一点和C语言的习惯一致,不用额外转换。
4.6 实战:组装一个完整的传感器数据包
光说不练假把式。咱们来写一个完整的例子:
cJSON *root = cJSON_CreateObject();
if (root == NULL) {
printf("创建JSON对象失败\n");
return;
}
cJSON_AddStringToObject(root, "device", "ESP32-S3");
cJSON_AddNumberToObject(root, "temp", 26.8);
cJSON_AddNumberToObject(root, "humidity", 55.3);
cJSON_AddNumberToObject(root, "pressure", 1013.25);
cJSON_AddBoolToObject(root, "is_valid", 1);
char *json_str = cJSON_Print(root);
if (json_str) {
printf("生成的JSON: %s\n", json_str);
free(json_str); // 注意:cJSON_Print分配的内存要手动释放
}
cJSON_Delete(root); // 释放整个JSON树
输出结果:
{
"device": "ESP32-S3",
"temp": 26.8,
"humidity": 55.3,
"pressure": 1013.25,
"is_valid": true
}
你看,整个过程很清晰。先CreateObject,然后Add各种数据,最后Print成字符串。记得释放内存,不然ESP32那点RAM很快就爆了。
4.7 常见错误与避坑指南
我总结几个新手容易犯的错:
- 忘记检查NULL:cJSON_CreateObject、cJSON_CreateString都可能返回NULL,尤其是内存不足时。不检查就直接用,必崩。
- 重复释放内存:cJSON_Delete会递归释放所有子节点。如果你手动free了某个子节点,再Delete就会double free。记住:只用cJSON_Delete释放整个树,不要手动释放子节点。
- 混淆cJSON_Print和cJSON_PrintUnformatted:前者输出带缩进和换行的格式化字符串,适合调试;后者输出紧凑的一行,适合网络传输。我建议调试用Print,上线用PrintUnformatted,省带宽。
- cJSON_CreateObject创建根对象,必须检查NULL
- cJSON_AddNumberToObject、AddStringToObject等是常用添加函数
- 数字默认用double存储,整数可能带小数点
- 字符串自动转义,不要手动拼接JSON
- 用完必须cJSON_Delete释放内存
好了,这一章就到这里。下一章我们讲如何解析JSON,也就是把收到的字符串变回C语言变量。到时候你会看到,解析比创建更考验细节。咱们下章见。