在现代嵌入式系统开发中,数据交换和通信变得日益重要尤其是在单片机设备接入云端的过程中经常会用到构建和解析JSON格式。并且JSON(JavaScript Object Notation)作为一种轻量级的数据交换格式,因其简洁、易于阅读和编写,以及跨平台兼容性的优势,在嵌入式系统中得到了广泛应用。本文将详细介绍如何在STM32微控制器上使用cJSON库来生成和解析JSON数据,为开发者们提供一个实用的指南。
一、准备工作
1. cJSON库简介
cJSON是一个轻量级的、易于使用的C语言库,用于解析和生成JSON数据。它只包含两个文件:cJSON.c
和cJSON.h
,非常适合资源受限的嵌入式系统。cJSON的设计目标是简单、快速和高效,因此它非常适合在STM32这样的微控制器上运行。
2. 硬件环境
- STM32微控制器(本次采用STM32F103RCT6)
- 串口通信模块
3. 软件环境
- Keil uVision IDE(或其他支持STM32的IDE)
- STM32CubeMX(可选,用于配置硬件)
- cJSON库文件(
cJSON.c
和cJSON.h
) - 工程采用之前制作的串口模版STM32-HAL库串口空闲中断DMA接收数据Demo-CSDN博客
二、实现步骤
1. 导入添加cJSON库
添加cjson.c和cjson.h文件到工程中,工程中相关文件放在工程目录下User文件夹下。
在json文件中可以看到已经封装好大量函数,在使用过程中只需要包含cjson.h文件便可以调用构建和解析json的函数
2. 创建JSON字符串
2.1. 基本用法
在STM32上创建一个JSON对象并添加数据非常简单。首先,需要创建一个cJSON
对象,然后使用cJSON提供的API函数向其中添加数据。如下代码所示能够在json对象中分别添加字符串string类型、数字以及bool类型:
cJSON *root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "name", "STM32"); cJSON_AddNumberToObject(root, "age",5); cJSON_AddBoolToObject(root, "is_running", cJSON_True);
复制
创建好JSON对象后,需要将其转换为字符串以便于传输或存储。cJSON提供了cJSON_Print
函数来完成这一任务。
char *json_string = cJSON_Print(root); printf("%s\n", json_string);
复制
现在就可以输出一个完整的json字符串:
{ "name": "stm32", "age": 5, "is_running": true }
复制
在实际使用过程中需要考虑到单片机内存空间问题,如果创建失败可能会导致内存泄漏等问题,所以在单片使用时需要增加相关判断和错误处理
2.2. 单片机中cJSON的用法
- 构建目标
下方json字符串为华为云IoTDA中设备上报属性的格式,在json字符串中嵌套json字符串
{ "name": "environment", "id": 1, "params": { "temp": 23.5, "humi": 50, "o2": 21 } }
复制
- 函数实现
在实现前需要确保stm32单片机的堆空间足够大,保证后续json构建能够成功。
如下代码能够实现了 cJSON 对象的创建、填充、转换为字符串、打印以及最终的内存释放。
void getJsonData(void) { cJSON *pOrder = cJSON_CreateObject(); cJSON *params = cJSON_CreateObject(); char *json_body = NULL; if (pOrder == NULL || params == NULL) { printf("Creat ENV JSONobj Err\n"); cJSON_Delete(pOrder); cJSON_Delete(params); return; } // 添加参数到params对象,并检查每个调用的返回值 if (!cJSON_AddNumberToObject(params,"temp",23.5)|| !cJSON_AddNumberToObject(params,"humi",50) || !cJSON_AddNumberToObject(params,"o2",21)) { printf("Add ENV data to JSONobj Err\n"); cJSON_Delete(pOrder); cJSON_Delete(params); return; } // 添加params到pOrder对象 cJSON_AddStringToObject(pOrder, "name", "environment"); cJSON_AddNumberToObject(pOrder, "id", 1); cJSON_AddItemToObject(pOrder, "params", params); // 转换JSON对象为字符串 json_body = cJSON_PrintUnformatted(pOrder); if (json_body == NULL) { printf("Print ENV JSON Err\n"); cJSON_Delete(pOrder); return; } // 打印并发送JSON字符串 printf("ENV json: %s \r\n", json_body); cJSON_free(json_body); cJSON_Delete(pOrder); }
复制
- 分析
- 错误检查:在创建 cJSON 对象后,检查
pOrder
和params
是否为NULL
。因为内存分配可能会失败,尤其是在资源受限的环境中。 - 参数添加:使用
cJSON_AddNumberToObject
添加了数字类型的参数到params
对象中,并检查了每个调用的返回值,能够确保数据正确添加到 JSON 对象中的好方法。 - 对象嵌套:使用
cJSON_AddItemToObject
将params
对象作为子项添加到了pOrder
对象中。params
的所有权转移给了pOrder
,因此在后续调用cJSON_Delete删除时不需要在之后单独删除params
。 - 字符串转换:使用
cJSON_PrintUnformatted
将pOrder
对象转换为了一个未格式化的字符串,并检查了转换是否成功。 - 内存释放:最后释放了转换后的字符串
json_body
所占用的内存,以及整个pOrder
对象及其所有子对象所占用的内存。
3. 解析JSON字符串
3.1. 基本用法
接收到的JSON字符串可以使用cJSON_Parse
函数进行解析。解析后的结果是一个cJSON
对象,你可以通过cJSON_GetObjectItem
等函数来访问其中的数据。
cJSON *parsed_json = cJSON_Parse(json_string); if (parsed_json == NULL) { // 解析失败处理 printf("Parse error\n"); } else { cJSON *name = cJSON_GetObjectItem(parsed_json, "name"); if (name != NULL) { printf("Name: %s\n", name->valuestring); } // 继续解析其他数据... cJSON_Delete(parsed_json); }
复制
3.2. 单片机中的用法
在stm32单片机中需要先判断接收到的字符串是否完整,并对一些错误进行防范。解析将以华为云IoTDA中应用侧下发的控制命令为例,其命令格式如下:
{ "object_device_id": 123, "command_name": "LED", "service_id": "WaterMeter", "paras": { "sw": true, "val": 50 } }
复制
其中paras中的sw代表开关,val代表亮度值,解析代码如下,第一部分先查找接收到的数据中完整的json字符串,第二部分分配内存空间复制保存json数据,第三部分解析json数据
int8_t Parse_MqttCmd(uint8_t *data) { // 寻找JSON数据的开始位置 const char *json_start = strstr((char *)data, "{"); if (json_start == NULL) { printf("JSON data not found in the received string.\n"); return -1; } size_t json_length = strlen(json_start); // 分配内存并复制JSON数据 char *json_data = (char *)malloc(json_length + 1); if (json_data == NULL) { printf("Memory allocation failed.\n"); return -1; } strncpy(json_data, json_start, json_length); json_data[json_length] = '\0'; // 添加null终止符 //解析JSON数据 cJSON *root = cJSON_Parse(json_data); if (root == NULL) { printf("Failed to parse JSON data.\n"); cJSON_free(json_data); return -1; } // ... 在这里处理JSON数据 ... // 获取并打印"command_name"字段的值 cJSON *name = cJSON_GetObjectItemCaseSensitive(root, "command_name"); //判断"command_name"字段的值选择控制类型 if (name && cJSON_IsString(name) && (name->valuestring != NULL)) { char * command_name = name->valuestring; printf("Name: %s \r\n", command_name); //灯光控制命令 if(strstr(name->valuestring,"LED")) { cJSON *paras = cJSON_GetObjectItemCaseSensitive(root, "paras");/*获取obj中的paras的json*/ if (paras && cJSON_IsObject(paras)) { cJSON *sw_item = cJSON_GetObjectItemCaseSensitive(paras, "sw");/*开关状态*/ if (sw_item != NULL && cJSON_IsBool(sw_item)) { int sw_value = sw_item->valueint; // cJSON使用int来表示bool值 printf("sw: %d \r\n", sw_value); // 输出sw的值,1代表true,0代表false }else { printf("Failed to get 'sw' value or it's not a boolean.\r\n"); } cJSON *val_item = cJSON_GetObjectItemCaseSensitive(paras, "val"); if (val_item != NULL && cJSON_IsNumber(val_item)) { int val = val_item->valueint; // cJSON使用int来表示bool值 printf("val: %d \r\n", val); // 输出sw的值,1代表true,0代表false }else { printf("Failed to get 'val' value or it's not a number. \r\n"); } } } } // 释放资源 cJSON_Delete(root); cJSON_free(json_data); return 1; }
复制
- 分析
- 字段存在性和类型检查:通过
cJSON_GetObjectItemCaseSensitive
和相应的类型检查函数来确保字段存在且类型正确。 - 内存分配和释放:分配内存来存储 JSON 字符串,并在处理完 JSON 数据后释放了这块内存也释放了 cJSON 对象。
- 错误处理:在JSON 解析失败和内存分配失败添加了错误提示。
4. 注意事项
- 内存管理:STM32的内存资源有限,特别是在使用cJSON库时,需要确保有足够的堆内存来存储JSON数据,需要调整STM32的堆栈大小。
- 错误处理:在解析JSON字符串时,一定要检查返回值,确保解析没有失败。
三、cJSON库各类函数解析
1. 解析和创建函数
- cJSON_Parse(): 解析一个JSON格式的字符串,并返回一个
cJSON
指针,指向解析后的JSON结构的根。如果解析失败,返回NULL
。 - cJSON_Create 系列函数(如
cJSON_CreateObject()
,cJSON_CreateArray()
,cJSON_CreateString()
,cJSON_CreateNumber()
,cJSON_CreateBool()
,cJSON_CreateNull()
): 这些函数用于创建不同类型的cJSON
对象,分别对应于JSON中的对象、数组、字符串、数字、布尔值和null。
2. 访问和修改函数
- cJSON_GetObjectItemCaseSensitive() 和 cJSON_GetObjectItem(): 根据键名查找JSON对象中的项。第一个函数对大小写敏感,第二个函数则不敏感。
- cJSON_GetArrayItem(): 根据索引获取JSON数组中的项。
- cJSON_AddItemToObject(), cJSON_AddItemToArray(), cJSON_AddItemReferenceToArray(), cJSON_AddItemToObjectCS(): 这些函数用于向JSON对象或数组中添加项。其中,带有
CS
后缀的函数对键名大小写敏感。 - cJSON_ReplaceItemInObjectCaseSensitive() 和 cJSON_ReplaceItemInObject(): 根据键名替换JSON对象中的项。与
GetObjectItem
函数类似,第一个函数对大小写敏感。 - cJSON_DetachItemFromObject() 和 cJSON_DetachItemFromArray(): 从JSON对象或数组中移除项,但不释放其内存。
- cJSON_DeleteItemFromObject() 和 cJSON_DeleteItemFromArray(): 从JSON对象或数组中移除项,并释放其内存。
3. 辅助函数
- cJSON_Print() 和 cJSON_PrintUnformatted(): 将
cJSON
结构转换回JSON格式的字符串。第一个函数格式化输出,第二个函数不格式化。 - cJSON_PrintBuffered(): 类似于
cJSON_Print
,但允许使用自定义的缓冲区。 - cJSON_Duplicate(): 创建一个
cJSON
项的深拷贝。 - cJSON_NumStrings 和相关宏(如
cJSON_IsArray
,cJSON_IsObject
,cJSON_IsString
,cJSON_IsNumber
,cJSON_IsBool
,cJSON_IsNull
): 这些函数和宏用于检查cJSON
项的类型。
4. 错误处理
- cJSON_GetErrorPtr(): 返回一个指向全局变量的指针,该变量包含解析错误时的错误信息(如果有的话)。
5. 内存管理
- cJSON_Delete(): 释放一个
cJSON
项及其所有子项占用的内存。
四、源码链接
【免费】stm32+cjson库实现json格式创建与解析资源-CSDN文库