当前位置: 首页 > ai >正文

JSON 和 cJSON 库入门教程

第一部分:了解 JSON (JavaScript Object Notation)

  1. 什么是 JSON?

    • JSON 是一种轻量级的数据交换格式
    • 易于人阅读和编写,同时也易于机器解析和生成
  • JSON 基于 JavaScript 编程语言的一个子集,但它是一种独立于语言的文本格式。许多编程语言都有解析和生成 JSON 数据的库。
  1. JSON 的基本结构
    JSON 数据由两种基本结构组成:

    • 对象 (Object)
      • 表示为 {} (花括号)包裹的无序的键/值对 (key/value pairs)集合。
      • 键 (key) 必须是字符串(用双引号 "" 包裹)。
      • 值 (value) 可以是字符串、数字、布尔值 (true/false)、数组、另一个 JSON 对象,或者 null 值。
      • 键和值之间用冒号 : 分隔,键值对之间用逗号 , 分隔。
      • 示例:
        {"name": "ESP32","cores": 2,"isWireless": true
        }
        
    • 数组 (Array)
      • 表示为 [] (方括号)包裹的有序的值的集合。
      • 值之间用逗号 , 分隔。
      • 数组中的值可以是不同类型的。
      • 示例:
        {"sensors": ["temperature", "humidity", "pressure"],"ports": [80, 443, 1883]
        }
        
  2. JSON 数据类型

    • 字符串 (String):由双引号 "" 包裹的 Unicode 字符序列。例如:"hello", "main.c"
    • 数字 (Number):整数或浮点数。例如:101, -5.5, 3.14159e-2
    • 布尔值 (Boolean)truefalse
    • null:表示空值或无值。
    • 对象 (Object):如上所述,键值对的集合。
    • 数组 (Array):如上所述,值的有序列表。
  3. 一个完整的 JSON 示例
    下面是一个更复杂的 JSON 示例,结合了对象和数组:

    {"deviceName": "SmartLight_01","location": "Living Room","status": {"isOn": true,"brightness": 75,"colorRGB": [255, 255, 0]},"supportedModes": ["Normal", "Night", "Reading"],"firmwareVersion": null
    }
    

    在这个例子中:

    • 最外层是一个 JSON 对象。
    • "deviceName" 的值是一个字符串。
    • "status" 的值是另一个 JSON 对象。
    • "colorRGB" (在 "status" 对象内部) 的值是一个包含三个数字的数组。
    • "supportedModes" 的值是一个包含三个字符串的数组。
    • "firmwareVersion" 的值是 null

第二部分:cJSON 库入门 (C 语言处理 JSON)

  1. 什么是 cJSON?

    • cJSON 是一个用标准 ANSI C 编写的超轻量级 JSON 解析器和生成器
    • 它的设计目标是小巧、快速且易于使用,非常适合在资源受限的系统(如微控制器 ESP32)上处理 JSON 数据。
  2. 为什么在 C 语言中使用 cJSON?

    • 解析 (Parsing):当你的 C 程序接收到一个 JSON 格式的字符串(例如,从网络、文件或传感器),cJSON 可以帮助你将这个字符串转换成 C 语言可以理解和操作的数据结构。
    • 生成 (Generating):当你的 C 程序需要发送 JSON 格式的数据时,cJSON 可以帮助你将 C 语言中的数据结构转换成 JSON 格式的字符串。
    • 操作 (Manipulating):一旦 JSON 被解析,你可以使用 cJSON 提供的函数来访问、检查和修改数据。
  3. cJSON 的核心概念:cJSON 结构体

    • 在 cJSON 中,所有 JSON 元素(无论是对象、数组、字符串、数字还是布尔值)都被表示为一个指向 cJSON 结构体的指针 (cJSON*)。
    • 这个结构体内部有一个 type 字段,它指明了该 cJSON 节点代表的 JSON 数据类型(例如 cJSON_Object, cJSON_Array, cJSON_String, cJSON_Number, cJSON_True, cJSON_False, cJSON_NULL)。
/* cJSON Types: */
#define cJSON_Invalid (0)
#define cJSON_False  (1 << 0)
#define cJSON_True   (1 << 1)
#define cJSON_NULL   (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array  (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw    (1 << 7) /* raw json *//* The cJSON structure: */
typedef struct cJSON
{/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */struct cJSON *next;struct cJSON *prev;/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */struct cJSON *child;/* The type of the item, as above. */int type;/* The item's string, if type==cJSON_String  and type == cJSON_Raw */char *valuestring;/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */int valueint;/* The item's number, if type==cJSON_Number */double valuedouble;/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */char *string;
} cJSON;

根据类型的不同,结构体中还有其他字段来存储实际的值(如 valuestring 存字符串,valueintvaluedouble 存数字)。

  1. cJSON 主要函数和用法

    首先,你需要在使用 cJSON 的 C 文件中包含其头文件:

    #include "cJSON.h"
    
    • 解析 JSON 字符串 -> cJSON 对象
      cJSON* cJSON_Parse(const char *value);
      这个函数接收一个 JSON 格式的 C 字符串,并尝试将其解析成一个 cJSON 对象树。

      • 如果解析成功,它返回指向树的根节点的 cJSON* 指针。
      • 如果解析失败(例如 JSON 格式错误),它返回 NULL。你可以用 cJSON_GetErrorPtr() 获取错误发生的大致位置。
    • 重要: cJSON_Parse 创建的 cJSON 对象及其所有子节点都会分配内存。使用完毕后,必须调用 cJSON_Delete() 来释放这些内存,以避免内存泄漏。

      const char *json_data_string = "{\"sensor\":\"temperature\", \"value\":25.5, \"unit\":\"C\"}";
      cJSON *root = cJSON_Parse(json_data_string);if (root == NULL) {const char *error_ptr = cJSON_GetErrorPtr();if (error_ptr != NULL) {fprintf(stderr, "Error before: %s\n", error_ptr);}// 处理解析错误...
      } else {// 解析成功,可以开始使用 root// ...
      }
      
        
    *   **释放 `cJSON` 对象内存**`void cJSON_Delete(cJSON *item);`这个函数会递归地释放 `item` 指向的 `cJSON` 对象及其所有子节点占用的内存。```c// ... 使用完 root 后 ...cJSON_Delete(root);root = NULL; // 良好习惯,防止悬挂指针
    
    • 访问 JSON 对象中的成员
      cJSON* cJSON_GetObjectItem(const cJSON *object, const char *string);
      或者大小写不敏感的版本:cJSON* cJSON_GetObjectItemCaseSensitive(const cJSON *object, const char *string); (通常用前者)
      这个函数从一个 cJSON_Object 类型的节点中,根据键名(一个 C 字符串)查找对应的成员。

      • 如果找到,返回指向该成员的 cJSON* 指针。
    • 如果未找到,返回 NULL

      // 假设 root 指向上面解析得到的 {"sensor":"temperature", "value":25.5, "unit":"C"}
      cJSON *sensor_item = cJSON_GetObjectItem(root, "sensor");
      cJSON *value_item = cJSON_GetObjectItem(root, "value");
      cJSON *unit_item = cJSON_GetObjectItem(root, "unit");// 检查类型并获取值
      if (sensor_item != NULL && cJSON_IsString(sensor_item)) {printf("Sensor Type: %s\n", sensor_item->valuestring);
      }
      if (value_item != NULL && cJSON_IsNumber(value_item)) {printf("Value: %f\n", value_item->valuedouble); // 或者 value_item->valueint (如果是整数)
      }
      if (unit_item != NULL && cJSON_IsString(unit_item) && unit_item->valuestring != NULL) {printf("Unit: %s\n", unit_item->valuestring);
      }
      
    • 访问 JSON 数组中的元素
      cJSON* cJSON_GetArrayItem(const cJSON *array, int index);
      获取数组中指定索引处的元素。索引从 0 开始。
      int cJSON_GetArraySize(const cJSON *array);
      获取数组中元素的数量。

      // 假设 json_array_string = "{\"values\":[10, 20, 30]}"
      // cJSON *root_array_obj = cJSON_Parse(json_array_string);
      // cJSON *values_array = cJSON_GetObjectItem(root_array_obj, "values");
      // if (values_array != NULL && cJSON_IsArray(values_array)) {
      //     int size = cJSON_GetArraySize(values_array);
      //     for (int i = 0; i < size; i++) {
      //         cJSON *element = cJSON_GetArrayItem(values_array, i);
      //         if (element != NULL && cJSON_IsNumber(element)) {
      //             printf("Array element %d: %d\n", i, element->valueint);
      //         }
      //     }
      // }
      // cJSON_Delete(root_array_obj);
      
    • 检查节点类型
      cJSON 提供了一系列 cJSON_Is<Type>() 函数来检查一个 cJSON 节点的类型:

      • cJSON_IsObject(const cJSON * const item)
      • cJSON_IsArray(const cJSON * const item)
      • cJSON_IsString(const cJSON * const item)
      • cJSON_IsNumber(const cJSON * const item)
      • cJSON_IsTrue(const cJSON * const item)
      • cJSON_IsFalse(const cJSON * const item)
      • cJSON_IsBool(const cJSON * const item) (检查是 TrueFalse)
      • cJSON_IsNull(const cJSON * const item)
        在使用 valuestring, valueint, valuedouble 之前,务必先用这些函数检查类型,以避免错误。
    • 获取节点的值
      一旦确定了类型,你可以通过 cJSON 结构体的成员访问其实际值:

      • item->valuestring: (类型是 char*) 对于字符串节点。
      • item->valueint: (类型是 int) 对于数字节点(通常是整数部分)。
      • item->valuedouble: (类型是 double) 对于数字节点(浮点数值)。
      • 对于布尔值,直接使用 cJSON_IsTrue()cJSON_IsFalse() 判断。
    • 创建 JSON 结构
      你也可以从零开始构建 JSON 结构:

      • cJSON* cJSON_CreateObject(void);
      • cJSON* cJSON_CreateArray(void);
      • cJSON* cJSON_CreateString(const char *string);
      • cJSON* cJSON_CreateNumber(double num);
      • cJSON* cJSON_CreateBool(cJSON_bool boolean); (传入 1 代表 true, 0 代表 false)
      • cJSON* cJSON_CreateNull(void);
    • 向对象/数组中添加成员

      • void cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); (将 item 添加到 object 中,键为 string)
      • void cJSON_AddItemToArray(cJSON *array, cJSON *item); (将 item 添加到 array 的末尾)
      • 注意:当一个 cJSON 节点通过这些函数被添加到另一个节点(父节点)后,它的内存管理就由父节点负责了。你不需要单独删除这个被添加的子节点;它会在父节点被 cJSON_Delete() 时一起被删除。
      • 还有一些便捷的宏,例如:
        • cJSON_AddStringToObject(object, "key", "value_string");
        • cJSON_AddNumberToObject(object, "key", 123);
      cJSON *new_json_obj = cJSON_CreateObject();
      if (!new_json_obj) { /* error handling */ }cJSON_AddStringToObject(new_json_obj, "command", "SET_LED");
      cJSON_AddNumberToObject(new_json_obj, "led_id", 1);
      cJSON_AddTrueToObject(new_json_obj, "state"); // state: truecJSON *params_array = cJSON_CreateArray();
      if (!params_array) { /* error handling */ cJSON_Delete(new_json_obj); return; }
      cJSON_AddItemToArray(params_array, cJSON_CreateNumber(255));
      cJSON_AddItemToArray(params_array, cJSON_CreateNumber(0));
      cJSON_AddItemToArray(params_array, cJSON_CreateNumber(0)); // color red
      cJSON_AddItemToObject(new_json_obj, "color_rgb", params_array); // 添加数组到对象// 现在 new_json_obj 代表: {"command":"SET_LED", "led_id":1, "state":true, "color_rgb":[255,0,0]}
      
    • cJSON 对象转换回 JSON 字符串

      • char* cJSON_Print(const cJSON *item);:生成带格式(易读,有换行和缩进)的 JSON 字符串。
      • char* cJSON_PrintUnformatted(const cJSON *item);:生成不带格式(紧凑,无额外空白)的 JSON 字符串。在网络传输时,通常用这个来节省带宽。
      • 重要: 这两个函数返回的字符串是动态分配内存的(使用 malloc)。你必须在使用完毕后调用 free() (或者 cJSON_free(),它们通常是等价的) 来释放这块内存。
      // ... 接上一个创建的 new_json_obj ...
      char *formatted_string = cJSON_Print(new_json_obj);
      if (formatted_string) {printf("Formatted JSON:\n%s\n", formatted_string);free(formatted_string); // 或者 cJSON_free(formatted_string)
      }char *unformatted_string = cJSON_PrintUnformatted(new_json_obj);
      if (unformatted_string) {printf("Unformatted JSON: %s\n", unformatted_string);// 通常这个字符串用于发送到网络或保存free(unformatted_string); // 或者 cJSON_free(unformatted_string)
      }cJSON_Delete(new_json_obj); // 最后删除 cJSON 对象本身
      
  2. 在你的 main.c 中的应用举例 (parse_m2m_message 函数)
    给出一个示例,一个自定义函数 parse_m2m_message 函数是如何使用 cJSON 的:

    // static int parse_m2m_message(const char *data, int data_len)
    // {// ...// char *data_copy = (char *)calloc(data_len + 1, sizeof(char)); // 创建数据副本// memcpy(data_copy, data, data_len);// data_copy[data_len] = '\0'; // 确保NULL结尾//// cJSON *root = cJSON_Parse(data_copy); // 1. 解析JSON字符串// if (root == NULL) { /* 错误处理 */ free(data_copy); return 0; }//// cJSON *from = cJSON_GetObjectItem(root, "from"); // 2. 获取对象成员// if (!from || !cJSON_IsString(from)) { /* 错误处理 */ }// printf("消息来源设备: %s\n", from->valuestring); // 3. 获取字符串值//// cJSON *cmd = cJSON_GetObjectItem(root, "cmd");// if (cmd && cJSON_IsString(cmd)) {//     cJSON *value = cJSON_GetObjectItem(root, "value");//     if (!value || !cJSON_IsNumber(value)) { /* 错误处理 */ }//     printf("收到命令类型: %s, 命令值: %d\n", cmd->valuestring, value->valueint); // 获取数字值//     // ... 进一步处理命令 ...// }// ...// cJSON_Delete(root); // 4. 释放内存// free(data_copy);// return 1;
    // }
    

    这个函数清晰地展示了 cJSON 的基本流程:解析字符串、获取对象成员、检查类型、获取值,最后释放内存。


总结

  • JSON 是一种通用的数据格式,而 cJSON 是在 C 环境中处理这种格式的得力工具。
  • 核心流程
    1. 解析: 字符串 -> cJSON_Parse() -> cJSON 对象。
    2. 访问/操作: cJSON_GetObjectItem(), cJSON_GetArrayItem(), 检查类型,获取 valuestring/valueint/valuedouble
    3. 创建: cJSON_Create<Type>(), cJSON_AddItemTo<Object/Array>()
    4. 序列化: cJSON 对象 -> cJSON_Print()/cJSON_PrintUnformatted() -> 字符串。
    5. 内存管理: cJSON_Delete() 用于释放 cJSON 对象树,free() (或 cJSON_free()) 用于释放 cJSON_Print 返回的字符串。
http://www.xdnf.cn/news/5949.html

相关文章:

  • SPI接口:原理;从设备slave如何主动给主设备master发数据?
  • 基于MNIST数据集的手写数字识别(简单全连接网络)
  • 共享代理IP带宽受限影响大吗
  • SQL:MySQL函数:数学函数(Mathematical Functions)
  • 牛客周赛96补题 D F
  • 【IC验证】systemverilog_类
  • yum安装-此系统没有注册
  • Python打包工具PyInstaller,打包之后的反编译工具pyinstxtractor
  • 2025.05.10京东机考真题算法岗-第一题
  • QT 插槽实现
  • 最短路与拓扑(1)
  • openjdk底层汇编指令调用(三)——编码
  • Ensemble Alignment Subspace Adaptation Method for Cross-Scene Classification
  • HDFS的客户端操作(1)
  • USB3.0拓展坞制作学习
  • Linux系统编程---Signal信号集
  • Profibus DP主站转Modbus RTU/TCP如何把E+H流量计接入到modbus
  • 基于单片机的视力保护仪设计与实现
  • 硬密封保温 V 型球阀:恒温工况下复杂介质控制的性价比之选-耀圣
  • RabbitMQ 核心概念与消息模型深度解析(一)
  • Linux 系统如何挂载U盘
  • 火语言RPA--EcshopV4发布商品
  • 【datawhale组队学习】coze-ai-assistant TASK01
  • 【ROS2实战】在中国地区 Ubuntu 22.04 上安装 ROS 2 Humble 教程
  • 黑白浮生项目测试报告
  • k8s初始化时候,报错无法通过 CRI(容器运行时接口)与 containerd 通信
  • 5.13 note
  • Java反射详细介绍
  • AI 检测原创论文:技术迷思与教育本质的悖论思考
  • 组策略+注册表解决 系统还原 被禁问题