一、JSON简介
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,具有以下核心特性:
- 完全独立于编程语言的文本格式
- 易于人阅读和编写
- 易于机器解析和生成
- 基于ECMAScript标准子集
1.1 JSON语法规则
{
"name": "ESP32",
"cores": 2,
"features": ["WiFi", "Bluetooth", "Low Power"],
"metadata": {
"voltage": 3.3,
"package": "QFN48"
}
}
- 对象:花括号
{}
包裹的键值对集合 - 数组:方括号
[]
包裹的值列表 - 键名:必须使用双引号包裹
- 分隔符:键值对用逗号分隔
1.2 支持的数据类型
类型 | 示例 |
---|---|
字符串 | “ESP32” |
数值 | 3.3, 240 |
布尔值 | true, false |
对象 | { “key”: “value” } |
数组 | [1, 2, 3] |
null | null |
二、添加cJSON库
在ESP-IDF环境中,cJSON库已集成在组件中:
// 包含头文件
#include "cJSON.h"
// CMakeLists.txt 配置
idf_component_register(
...
REQUIRES cJSON
)
三、生成JSON数据
3.1 创建JSON结构体
cJSON *root = cJSON_CreateObject(); // 创建根对象
cJSON *sensor_data = cJSON_CreateObject(); // 创建子对象
3.2 添加各种类型数据
// 添加基本类型
cJSON_AddStringToObject(root, "device", "ESP32-S3");
cJSON_AddNumberToObject(root, "temperature", 25.6);
cJSON_AddBoolToObject(root, "connected", true);
// 添加嵌套对象
cJSON_AddItemToObject(root, "sensor", sensor_data);
cJSON_AddStringToObject(sensor_data, "type", "DHT11");
cJSON_AddNumberToObject(sensor_data, "humidity", 45.7);
// 添加整型数组
int gpio_pins[3] = {12, 13, 14};
cJSON *pins_array = cJSON_CreateIntArray(gpio_pins, 3);
cJSON_AddItemToObject(root, "gpio_pins", pins_array);
// 添加对象数组
cJSON *networks = cJSON_CreateArray();
for (int i = 0; i < 2; i++) {
cJSON *network = cJSON_CreateObject();
cJSON_AddStringToObject(network, "ssid", (i == 0) ? "HomeWiFi" : "OfficeAP");
cJSON_AddNumberToObject(network, "rssi", -65 + i*10);
cJSON_AddItemToArray(networks, network);
}
cJSON_AddItemToObject(root, "networks", networks);
3.3 输出与序列化
// 格式化输出
char *json_str = cJSON_Print(root);
ESP_LOGI("JSON", "Generated JSON:\n%s", json_str);
/* 输出结果:
{
"device": "ESP32-S3",
"temperature": 25.6,
"connected": true,
"sensor": {
"type": "DHT11",
"humidity": 45.7
},
"gpio_pins": [12, 13, 14],
"networks": [
{"ssid": "HomeWiFi", "rssi": -65},
{"ssid": "OfficeAP", "rssi": -55}
]
}
*/
3.4 内存管理
cJSON_free(json_str); // 释放打印字符串
cJSON_Delete(root); // 递归释放整个JSON树
重要提示:
cJSON_Delete()
会递归释放所有子节点,只需在根节点调用一次
四、解析JSON数据
4.1 基础解析流程
const char *json_str = "{\"status\":\"active\",\"uptime\":3600}";
cJSON *root = cJSON_Parse(json_str);
if (root == NULL) {
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr != NULL) {
ESP_LOGE("JSON", "Error before: %s", error_ptr);
}
return;
}
// 提取数据
cJSON *status = cJSON_GetObjectItem(root, "status");
cJSON *uptime = cJSON_GetObjectItem(root, "uptime");
if (cJSON_IsString(status)) {
ESP_LOGI("JSON", "Status: %s", status->valuestring);
}
if (cJSON_IsNumber(uptime)) {
ESP_LOGI("JSON", "Uptime: %d seconds", uptime->valueint);
}
cJSON_Delete(root);
4.2 解析复杂结构
// 示例JSON
const char *config_str = "{ \"sensors\": [ \
{\"type\":\"temperature\", \"pin\":12}, \
{\"type\":\"humidity\", \"pin\":14} \
]}";
cJSON *root = cJSON_Parse(config_str);
cJSON *sensors = cJSON_GetObjectItem(root, "sensors");
if (cJSON_IsArray(sensors)) {
int sensor_count = cJSON_GetArraySize(sensors);
for (int i = 0; i < sensor_count; i++) {
cJSON *sensor = cJSON_GetArrayItem(sensors, i);
cJSON *type = cJSON_GetObjectItem(sensor, "type");
cJSON *pin = cJSON_GetObjectItem(sensor, "pin");
if (cJSON_IsString(type) && cJSON_IsNumber(pin)) {
ESP_LOGI("SENSOR", "Type: %s, GPIO: %d",
type->valuestring, pin->valueint);
}
}
}
4.3 错误处理技巧
// 安全获取对象项
cJSON* safe_get_object(cJSON *obj, const char *key, cJSON_type type) {
cJSON *item = cJSON_GetObjectItem(obj, key);
if (!item) {
ESP_LOGW("JSON", "Missing key: %s", key);
return NULL;
}
if (item->type != type) {
ESP_LOGW("JSON", "Type mismatch for %s", key);
return NULL;
}
return item;
}
// 使用示例
cJSON *temp = safe_get_object(root, "temperature", cJSON_Number);
if (temp) {
// 安全使用数据
}
五、实战技巧与最佳实践
5.1 内存优化技巧
// 使用缓冲器避免碎片化
char json_buffer[512];
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "message", "Hello JSON");
// 直接打印到固定缓冲区
cJSON_PrintPreallocated(root, json_buffer, sizeof(json_buffer), true);
ESP_LOGI("JSON", "Compact JSON: %s", json_buffer);
// 释放资源
cJSON_Delete(root);
5.2 常用库函数速查
函数 | 说明 |
---|---|
cJSON_Parse() | 解析JSON字符串 |
cJSON_Print() | 生成格式化JSON字符串 |
cJSON_PrintUnformatted() | 生成紧凑型JSON字符串 |
cJSON_GetObjectItem() | 获取对象属性 |
cJSON_GetArraySize() | 获取数组长度 |
cJSON_GetArrayItem() | 获取数组元素 |
cJSON_Is...() | 类型检查函数族 |
cJSON_Add...ToObject() | 添加各类数据到对象 |
cJSON_Create...() | 创建各类JSON元素 |
5.3 常见问题解决方案
- 内存泄漏:确保每个
cJSON_Create*()
都有对应的cJSON_Delete()
- 无效指针:检查
cJSON_Parse()
返回值是否为NULL - 类型错误:使用
cJSON_IsNumber()
等函数验证数据类型 - 键名错误:使用
cJSON_HasObjectItem()
检查键是否存在
性能提示:在实时系统中避免频繁解析大型JSON,可考虑预解析或二进制格式
结语
cJSON为ESP32提供了高效的JSON处理能力,本文涵盖了从基础操作到高级技巧的全方位内容。实际开发时请注意:
- 始终进行错误检查
- 在解析前验证JSON格式有效性
- 使用预分配缓冲区处理大数据
- 及时释放内存资源
示例代码兼容ESP-IDF v4.4+,可在GitHub获取完整工程:esp32-cjson-demo
附:内存管理示意图
cJSON_CreateObject()
├── cJSON_AddStringToObject() // 分配字符串内存
├── cJSON_CreateArray() // 分配数组内存
│ └── cJSON_CreateObject() // 嵌套对象
└── ...
cJSON_Print() // 分配输出字符串内存
cJSON_free() // 释放输出字符串
cJSON_Delete() // 递归释放所有节点