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,这是基本功。

⚠️ 重要提醒: cJSON_CreateObject返回的是一个指针,指向堆内存。用完必须释放,否则内存泄漏。后面会讲cJSON_Delete,别急。

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,或者自己格式化字符串。不过大多数物联网场景,小数点无所谓,后端解析时都会处理。

💡 小技巧: 如果你需要精确控制数字格式(比如保留两位小数),可以先sprintf成字符串,再用cJSON_AddStringToObject。但这样会增加解析开销,自己权衡。

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语言变量。到时候你会看到,解析比创建更考验细节。咱们下章见。