十二、【ESP32全栈开发指南: IDF开发环境下cJSON使用】

一、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]
nullnull

二、添加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 常见问题解决方案
  1. 内存泄漏:确保每个 cJSON_Create*() 都有对应的 cJSON_Delete()
  2. 无效指针:检查 cJSON_Parse() 返回值是否为NULL
  3. 类型错误:使用 cJSON_IsNumber() 等函数验证数据类型
  4. 键名错误:使用 cJSON_HasObjectItem() 检查键是否存在

性能提示:在实时系统中避免频繁解析大型JSON,可考虑预解析或二进制格式


结语

cJSON为ESP32提供了高效的JSON处理能力,本文涵盖了从基础操作到高级技巧的全方位内容。实际开发时请注意:

  1. 始终进行错误检查
  2. 在解析前验证JSON格式有效性
  3. 使用预分配缓冲区处理大数据
  4. 及时释放内存资源

示例代码兼容ESP-IDF v4.4+,可在GitHub获取完整工程:esp32-cjson-demo

附:内存管理示意图

cJSON_CreateObject()
  ├── cJSON_AddStringToObject()  // 分配字符串内存
  ├── cJSON_CreateArray()         // 分配数组内存
  │    └── cJSON_CreateObject()   // 嵌套对象
  └── ... 
cJSON_Print()           // 分配输出字符串内存
cJSON_free()            // 释放输出字符串
cJSON_Delete()          // 递归释放所有节点
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱睡觉的王宇昊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值