十二、【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]

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 常见问题解决方案

  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()          // 递归释放所有节点

发表评论