在Keil5中利用Jansson库处理和组装JSON数据
下载Keil.Jansson.1.0.0.pack
https://keilpack.azureedge.net/pack/Keil.Jansson.1.0.0.packhttps://keilpack.azureedge.net/pack/Keil.Jansson.1.0.0.pack
下载完成后直接安装到keil5中即可
选择Jansson库的理由:轻量、易用、适配嵌入式
Jansson库之所以被选用,主要基于以下三大核心优势:
- 轻量级:Jansson库设计紧凑,占用资源少,非常适合资源受限的嵌入式环境。
- 易用性:提供简洁直观的API,使得JSON数据的解析与组装变得简单快捷。
- 嵌入式适配:特别注重在嵌入式系统中的应用,支持多种平台,优化性能以减少资源消耗。
这些特点使得Jansson库成为嵌入式系统中处理JSON数据的优选工具。
配置Jansson
①:打开keil5后,点击工具栏中魔术棒的右边具有四个黑点的绿色正方形
②:点击Data Exchange: 里面可以看到安装的拓展库,例如这里显示JSON,
这里将下方的Jansson勾选
如果keil5左边项目文件中包含了这个图像和相关的jansson文件代表Jansson环境配置成功
配置printf
如果需要使用printf的话,这里放一个传送门
或者也可以直接使用串口发送函数
STM32基于HAL库串口printf使用和接收https://blog.csdn.net/Wang2869902214/article/details/141719519
这里无论是不是HAL库,都可以按照上方文章说明配置,
只需在重定向代码中更换对应的串口发送函数
解析json格式数据代码
这里使用STM32F103C8T6芯片作为测试硬件,软件方面需要准备一个串口调试助手,用来查看代码运行时的状态
文章中的代码只是用来举一反三,可以根据它的编程规律和函数命名规律,可以合理的推断出解析整形还是字符串还是浮点数的转换函数
这里注意一点:
如果Json解析代码是正确的,然后解析的内容与预期不符,很可能是程序中设置的堆大小不足,
解决方法:在启动文件中:startup_stm32f103xb.s中找到
Heap_Size EQU 0x200
修改为(也可以在STM32CubeMX软件代码生成时修改)
Heap_Size EQU 0x800
这里使用简单的字符串先模拟一下解析json格式的数据,提取里面每个Json对象的数据
创建了一个字符串:
const char *json_str = "{\"name\":\"Huan\",\"age\":19,\"city\":\"Mei Zhou\"}";
目的:提取出这些"name","age","city"字段的数据
完整代码1
#include "main.h"
#include <stdio.h>
#include "jansson.h"
const char *json_str = "{\"name\":\"Huan\",\"age\":19,\"city\":\"Mei Zhou\"}";
typedef struct
{
char name[10];
uint8_t age;
char city[10];
}Student_TypeDef;
uint8_t myJsonTest(const char *jsonString, Student_TypeDef *student)
{
// 解析JSON字符串
json_t *root;
json_error_t error;
root = json_loads(jsonString, 0, &error);
if (!root) {
fprintf(stderr, "Error parsing JSON: %s\r\n", error.text);
return 1;
}
// 检查JSON对象是否为有效对象
if (!json_is_object(root)) {
fprintf(stderr, "Error: JSON is not an object\r\n");
json_decref(root);
return 1;
}
// 提取并打印name字段
json_t *name_value = json_object_get(root, "name");
if (json_is_string(name_value)) {
strcpy(student->name, json_string_value(name_value));
} else {
printf("Name field is not a string\r\n");
}
// 提取并打印age字段
json_t *age_value = json_object_get(root, "age");
if (json_is_integer(age_value)) {
student->age = json_integer_value(age_value);
} else {
printf("Age field is not an integer\r\n");
}
// 提取并打印city字段
json_t *city_value = json_object_get(root, "city");
if (json_is_string(city_value)) {
strcpy(student->city, json_string_value(city_value));
} else {
printf("City field is not a string\r\n");
}
// 释放JSON对象
json_decref(root);
return 0;
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
while (1)
{
Student_TypeDef student = {{0}, 0, {0}};
printf("\r\n%s\r\n",json_str);
myJsonTest(json_str, &student);
printf("name:%s\r\n",student.name);
printf("age:%d\r\n",student.age);
printf("city:%s\r\n",student.city);
HAL_Delay(5000);
}
}
代码1运行效果
其他的数据类型转换方法就举一反三即可
这里注意一点:
如果Json解析代码是正确的,然后解析的内容与预期不符,很可能是程序中设置的堆大小不足,
解决方法:在启动文件中:startup_stm32f103xb.s中找到
Heap_Size EQU 0x200
修改为(也可以在STM32CubeMX软件代码生成时修改)
Heap_Size EQU 0x800
注意事项:上文提到加堆设置,是因为这里json初始化使用的是动态分配空间(malloc),如果堆设置范围太小,那么这里json解析时分配不了所需要的空间,会导致解析失败,包括后面要提到的组装json
json_loads(jsonString, 0, &error); //内部处理:动态开辟空间
既然是需要动态分配空间,那么在使用结束后也是需要释放空间的,否则运行几次程序就会异常
json_decref(root); //内部处理:释放空间
也正是因为这个代码的使用他需要开辟和释放空间,就说明在这个内存的生命周期结束后,里面所有的返回字符串指针全部会被清空,涉及到字符串的情况下,需要使用拷贝函数,将字符串拷贝到自己生命周期更长的变量中,例如上方的Student_TypeDef结构体
如果需要解析一个多重json对象,例如下方代码
const char *json_str = "{\"id\": \"123\",\"params\":{\"name\":{\"value\":\"Huan\"},\"age\":{\"value\":19},\"city\":{\"value\":\"Mei Zhou\"}}}";
//原型如下:
{
"id": "123",
"params": {
"name": {
"value": "Huan"
},
"age": {
"value": 19
},
"city": {
"value": "Mei Zhou"
}
}
}
完整代码2
#include "main.h"
#include <stdio.h>
#include "jansson.h"
const char *json_str = "{\"id\": \"123\",\"params\":{\"name\":{\"value\":\"Huan\"},\"age\":{\"value\":19},\"city\":{\"value\":\"Mei Zhou\"}}}";
typedef struct
{
char city[10];
char name[10];
char id[10];
uint8_t age;
}Student_TypeDef;
uint8_t myJsonTest(const char *jsonString, Student_TypeDef *student)
{
json_t *root;
json_error_t error;
root = json_loads(jsonString, 0, &error);
if (!root) {
fprintf(stderr, "Error parsing JSON: %s\r\n", error.text);
return 1;
}
if (!json_is_object(root)) {
fprintf(stderr, "Error: JSON is not an object\r\n");
json_decref(root);
return 1;
}
// 提取id的值
json_t *id_value = json_object_get(root, "id");
if (json_is_string(id_value)) {
//id = (char *)json_string_value(id_value);
strcpy(student->id, (char *)json_string_value(id_value));
} else {
printf("id is not a string\r\n");
}
json_t *params = json_object_get(root, "params");
if (json_is_object(params)) {
// 提取并打印name字段
json_t *name_obj = json_object_get(params, "name");
if (json_is_object(name_obj)) {
json_t *name_value = json_object_get(name_obj, "value");
if (json_is_string(name_value)) {
strncpy(student->name, json_string_value(name_value), sizeof(student->name) - 1);
} else {
printf("Name value is not a string\r\n");
}
} else {
printf("Name field is not an object\r\n");
}
// 提取并打印age字段
json_t *age_obj = json_object_get(params, "age");
if (json_is_object(age_obj)) {
json_t *age_value = json_object_get(age_obj, "value");
if (json_is_integer(age_value)) {
student->age = (uint8_t)json_integer_value(age_value);
} else {
printf("Age value is not an integer\r\n");
}
} else {
printf("Age field is not an object\r\n");
}
// 提取并打印city字段
json_t *city_obj = json_object_get(params, "city");
if (json_is_object(city_obj)) {
json_t *city_value = json_object_get(city_obj, "value");
if (json_is_string(city_value)) {
strncpy(student->city, json_string_value(city_value), sizeof(student->city) - 1);
} else {
printf("City value is not a string\r\n");
}
} else {
printf("City field is not an object\r\n");
}
} else {
printf("Params field is not an object\r\n");
}
json_decref(root);
return 0;
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
printf("Hello World!\r\n");
while (1)
{
Student_TypeDef student = {{0}, {0}, {0}, 0};
myJsonTest(json_str, &student);
printf("id:%s\r\n",student.id);
printf("name:%s\r\n",student.name);
printf("age:%d\r\n",student.age);
printf("city:%s\r\n",student.city);
HAL_Delay(5000);
}
}
代码2运行效果:
这里注意一点:
如果Json解析代码是正确的,然后解析的内容与预期不符,很可能是程序中设置的堆大小不足,
解决方法:在启动文件中:startup_stm32f103xb.s中找到
Heap_Size EQU 0x200
修改为(也可以在STM32CubeMX软件代码生成时修改)
Heap_Size EQU 0x800
组装json格式数据代码
这里注意一点:
如果Json组装代码是正确的,然后组装的内容与预期不符(可能时乱码也可能是空),很可能是程序中设置的堆大小不足,
解决方法:在启动文件中:startup_stm32f103xb.s中找到
Heap_Size EQU 0x200
修改为(也可以在STM32CubeMX软件代码生成时修改)
Heap_Size EQU 0x800
这里预计组装一个json格式的数据:
{ "name": "Huan", "age": 19, "city": "Mei Zhou"}
对于非常简单且固定的JSON格式数据,可以考虑直接使用snprintf
或类似的字符串格式化函数来生成JSON字符串。
示例:
#include <stdio.h>
#include <string.h>
// 定义全局缓冲区
#define BUFFER_SIZE 128
char global_buffer[BUFFER_SIZE];
// 函数声明
void create_json_string(const char* name, int age, const char* city);
// 主函数
int main(void)
{
create_json_string("Huan", 19, "Mei Zhou");
printf("Created JSON: %s\n", global_buffer);
return 0;
}
// 创建JSON字符串的函数
void create_json_string(const char* name, int age, const char* city)
{
// 使用snprintf将JSON字符串写入全局缓冲区
snprintf(global_buffer, BUFFER_SIZE, "{\"name\": \"%s\", \"age\": %d, \"city\": \"%s\"}", name, age, city);
}
完整代码1
#include "main.h"
#include <stdio.h>
#include "jansson.h"
#include <string.h> // 添加对字符串操作的支持
// 函数声明
char* create_json_string(const char* name, uint8_t age, const char* city);
// 主函数
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
while (1)
{
// 创建 JSON 字符串
char* json_str = create_json_string("Huan", 19, "Mei Zhou");
if (json_str != NULL) {
// 打印 JSON 字符串
printf("\r\nCreated JSON: %s\r\n", json_str);
// 释放 JSON 字符串内存
free(json_str); // 这里需要释放内存,因为json_dumps分配了新内存
}
HAL_Delay(5000);
}
}
// 创建 JSON 字符串的函数
char* create_json_string(const char* name, uint8_t age, const char* city)
{
// 创建 JSON 对象
json_t *root = json_object();
if (!root) {
fprintf(stderr, "Unable to create JSON object\r\n");
return NULL;
}
// 添加 name 字段
if (json_object_set_new(root, "name", json_string(name)) != 0) {
fprintf(stderr, "Unable to set name field\r\n");
json_decref(root);
return NULL;
}
// 添加 age 字段
if (json_object_set_new(root, "age", json_integer(age)) != 0) {
fprintf(stderr, "Unable to set age field\r\n");
json_decref(root);
return NULL;
}
// 添加 city 字段
if (json_object_set_new(root, "city", json_string(city)) != 0) {
fprintf(stderr, "Unable to set city field\r\n");
json_decref(root);
return NULL;
}
// 将 JSON 对象转换为字符串
char *jsonString = json_dumps(root, JSON_INDENT(2));
if (!jsonString) {
fprintf(stderr, "Unable to dump JSON string\r\n");
json_decref(root);
return NULL;
}
// 释放 JSON 对象
json_decref(root);
return jsonString;
}
代码1运行效果:
完整代码2
如果像组装多重嵌套json对象的话,例如:
{
"id": "123",
"params": {
"name": {
"value": "Huan"
},
"age": {
"value": 19
},
"city": {
"value": "Mei Zhou"
}
}
}
#include "main.h"
#include <stdio.h>
#include "jansson.h"
typedef struct {
char city[10];
char name[10];
char id[10];
uint8_t age;
} Student_TypeDef;
char* create_json_string(const Student_TypeDef *student) {
json_t *root, *params, *name_obj, *age_obj, *city_obj;
char *json_str;
// 创建根对象
root = json_object();
if (!root) {
fprintf(stderr, "Failed to create root JSON object\r\n");
return NULL;
}
// 设置 id 字段
json_object_set_new(root, "id", json_string(student->id));
// 创建 params 对象
params = json_object();
if (!params) {
fprintf(stderr, "Failed to create params JSON object\r\n");
json_decref(root);
return NULL;
}
// 创建并设置 name 对象及其 value 字段
name_obj = json_object();
json_object_set_new(name_obj, "value", json_string(student->name));
json_object_set_new(params, "name", name_obj);
// 创建并设置 age 对象及其 value 字段
age_obj = json_object();
json_object_set_new(age_obj, "value", json_integer(student->age));
json_object_set_new(params, "age", age_obj);
// 创建并设置 city 对象及其 value 字段
city_obj = json_object();
json_object_set_new(city_obj, "value", json_string(student->city));
json_object_set_new(params, "city", city_obj);
// 将 params 对象添加到根对象中
json_object_set_new(root, "params", params);
// 将 JSON 对象转换为字符串
json_str = json_dumps(root, JSON_INDENT(0)); // 使用缩进使输出更美观(可选)
// 释放 JSON 对象(注意:json_dumps 之后不需要再释放 root,因为 json_dumps 会复制数据)
json_decref(root);
return json_str;
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
printf("Hello World!\r\n");
while (1) {
Student_TypeDef student = {"Mei Zhou", "Huan", "123", 19};
char *json_str = create_json_string(&student);
if (json_str) {
printf("Generated JSON:\r\n%s\r\n", json_str);
free(json_str); // 释放动态分配的 JSON 字符串内存
} else {
printf("Failed to generate JSON\r\n");
}
HAL_Delay(5000);
}
}
代码2运行效果:
补充
在最后组装json数据时,这里可以填一些参数
char *jsonString = json_dumps(root, JSON_INDENT(2));
分别为:
#define JSON_MAX_INDENT 0x1F
#define JSON_INDENT(n) ((n) & JSON_MAX_INDENT)
#define JSON_COMPACT 0x20
#define JSON_ENSURE_ASCII 0x40
#define JSON_SORT_KEYS 0x80
#define JSON_PRESERVE_ORDER 0x100
#define JSON_ENCODE_ANY 0x200
#define JSON_ESCAPE_SLASH 0x400
#define JSON_REAL_PRECISION(n) (((n) & 0x1F) << 11)
宏定义 | 值 | 说明 |
---|---|---|
JSON_MAX_INDENT | 0x1F | 最大缩进级别(用于格式化输出时的缩进限制) |
JSON_INDENT(n) | ((n) & JSON_MAX_INDENT) | 设置JSON输出的缩进级别为n ,实际缩进级别会被限制在JSON_MAX_INDENT 范围内 |
JSON_COMPACT | 0x20 | 生成紧凑的JSON字符串(无缩进和多余的空格) |
JSON_ENSURE_ASCII | 0x40 | 确保输出的JSON字符串仅包含ASCII字符,对非ASCII字符进行转义 |
JSON_SORT_KEYS | 0x80 | 对JSON对象的键进行排序输出 |
JSON_PRESERVE_ORDER | 0x100 | 保留JSON对象插入键值的顺序(不排序) |
JSON_ENCODE_ANY | 0x200 | 允许编码任何类型的JSON值(包括非标准的或自定义的类型) |
JSON_ESCAPE_SLASH | 0x400 | 对正斜杠(/ )进行转义处理 |
JSON_REAL_PRECISION(n) | (((n) & 0x1F) << 11) | 设置浮点数的精度为n (n 的有效范围是0到31) |
用法:
char *jsonString = json_dumps(root, JSON_INDENT(2) | JSON_ENSURE_ASCII | JSON_REAL_PRECISION(5));
这个示例中,json_dumps
函数被配置为:
- 使用2个空格缩进
- 确保输出为ASCII字符
- 设置浮点数的精度为5
结尾(必读)
这里注意一点:
如果Json解析或者组装代码是正确的,然后解析或者组装的内容与预期不符,很可能是程序中设置的堆大小不足,
解决方法:在启动文件中:startup_stm32f103xb.s中找到
Heap_Size EQU 0x200
修改为(也可以在STM32CubeMX软件代码生成时修改)
Heap_Size EQU 0x800
在json解析和组装代码中存在大量的if判断结果是否正确,为了代码的健壮性一般不建议删除这些判断
后续更新更多的API使用