JSON数据格式C语言解析库(cJSON)的使用&在STM32上移植和使用
Posted 行稳方能走远
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JSON数据格式C语言解析库(cJSON)的使用&在STM32上移植和使用相关的知识,希望对你有一定的参考价值。
目录
轻量级C语言JSON解析库
参考博文:cJSON使用详细教程 | 一个轻量级C语言JSON解析器
作者:Mculover666
地址:https://mculover666.blog.csdn.net/article/details/103796256?spm=1001.2014.3001.5502
1.JSON与cJSON
JSON —— 轻量级的数据格式
JSON 全称 javascript Object Notation,即 JS对象简谱,是一种轻量级的数据格式。
它采用完全独立于编程语言的文本格式来存储和表示数据,语法简洁、层次结构清晰,易于人阅读和编写,同时也易于机器解析和生成,有效的提升了网络传输效率。
JSON语法规则
JSON对象是一个无序的"名称/值"键值对的集合:
- 以"“开始,以”"结束,允许嵌套使用,即对象里面嵌套对象;
- 每个名称和值成对出现(名称又叫做键,所以又叫键值对),名称和值之间使用":"分隔;
- 键值对之间用","分隔
- 在这些字符前后允许存在无意义的空白符;
对于键值,可以有如下值:
- 一个新的json对象
- 数组:使用"[“和”]"表示
- 数字:直接表示,可以是整数,也可以是浮点数
- 字符串:使用引号"表示
- 字面值:false、null、true中的一个(必须是小写)
注意:
详细的JSON数据格式参照:太极创客JSON基础
示例如下:
"name": "mculover666",
"age": 22,
"weight": 55.5,
"address":
//值是一个对象,即对象里面嵌套对象
"country": "China",
"zip-code": 111111
,
"skill": ["c", "Java", "Python"],//数组
"student": false //布尔
cJSON
cJSON是一个使用C语言编写的JSON数据解析器,具有超轻便,可移植,单文件的特点,使用MIT开源协议。
cJSON项目托管在Github上,仓库地址如下:
https://github.com/DaveGamble/cJSON
使用Git命令将其拉取到本地:
git clone https://github.com/DaveGamble/cJSON.git
从Github拉取cJSON源码后,文件非常多,但是其中cJSON的源码文件只有两个:
- cJSON.h
- cJSON.c
使用的时候,只需要将这两个文件复制到工程目录,然后包含头文件cJSON.h即可,如下:
#include "cJSON.h"
2.cJSON数据结构和设计思想
cJSON的设计思想从其数据结构上就能反映出来。
cJSON使用cJSON结构体来表示一个JSON数据,定义在cJSON.h中,源码如下:
/* 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;
cJSON的设计很巧妙。
首先,它不是将一整段JSON数据抽象出来,而是将其中的一条JSON数据抽象出来,也就是一个键值对,用上面的结构体 strcut cJSON 来表示,其中用来存放值的成员列表如下:
- String:用于表示该键值对的名称;
- type:用于表示该键值对中值的类型;
- valuestring:如果键值类型(type)是字符串,则将该指针指向键值;
- valueint:如果键值类型(type)是整数,则将该指针指向键值;
- valuedouble:如果键值类型(type)是浮点数,则将该指针指向键值;
其次,一段完整的JSON数据中由很多键值对组成,并且涉及到键值对的查找、删除、添加,所以使用链表来存储整段JSON数据,如上面的代码所示:
- next指针:指向下一个键值对
- prev指针指向上一个键值对
最后,因为JSON数据支持嵌套,所以一个键值对的值会是一个新的JSON数据对象(一条新的链表),也有可能是一个数组,方便起见,在cJSON中,数组也表示为一个数组对象,用链表存储,所以:
在键值对结构体中,当该键值对的值是一个嵌套的JSON数据或者一个数组时,由child指针指向该条新链表。
3.JSON数据封装
封装方法
封装JSON数据的过程,其实就是创建链表和向链表中添加节点的过程。
首先来讲述一下链表中的一些术语:
- 头指针:指向链表头结点的指针;
- 头结点:不存放有效数据,方便链表操作;
- 首节点:第一个存放有效数据的节点;
- 尾节点:最后一个存放有效数据的节点;
明白了这几个概念之后,我们开始讲述创建一段完整的JSON数据,即如何创建一条完整的链表。
① 创建头指针:
cJSON* cjson_test = NULL;
② 创建头结点,并将头指针指向头结点:
cjson_test = cJSON_CreateObject();
③ 尽情的向链表中添加节点:
cJSON_AddNullToObject(cJSON * const object, const char * const name);
cJSON_AddTrueToObject(cJSON * const object, const char * const name);
cJSON_AddFalseToObject(cJSON * const object, const char * const name);
cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
cJSON_AddObjectToObject(cJSON * const object, const char * const name);
cJSON_AddArrayToObject(cJSON * const object, const char * const name);
输出JSON数据
上面讲述,一段完整的JSON数据就是一条长长的链表,那么,如何打印出这段JSON数据呢?
cJSON提供了一个API,可以将整条链表中存放的JSON信息输出到一个字符串中:
(char *) cJSON_Print(const cJSON *item);
使用的时候,只需要接收该函数返回的指针地址即可。
封装数据和打印数据示例
单纯的讲述方法还不够,下面用一个例子来说明,封装出开头给出的那段JSON数据:
#include <stdio.h>
#include "cJSON.h"
int main(void)
cJSON* cjson_test = NULL;
cJSON* cjson_address = NULL;
cJSON* cjson_skill = NULL;
char* str = NULL;
/* 创建一个JSON数据对象(链表头结点) */
cjson_test = cJSON_CreateObject();
/* 添加一条字符串类型的JSON数据(添加一个链表节点) */
cJSON_AddStringToObject(cjson_test, "name", "mculover666");
/* 添加一条整数类型的JSON数据(添加一个链表节点) */
cJSON_AddNumberToObject(cjson_test, "age", 22);
/* 添加一条浮点类型的JSON数据(添加一个链表节点) */
cJSON_AddNumberToObject(cjson_test, "weight", 55.5);
/* 添加一个嵌套的JSON数据(添加一个链表节点) */
cjson_address = cJSON_CreateObject();
cJSON_AddStringToObject(cjson_address, "country", "China");
cJSON_AddNumberToObject(cjson_address, "zip-code", 111111);
cJSON_AddItemToObject(cjson_test, "address", cjson_address);
/* 添加一个数组类型的JSON数据(添加一个链表节点) */
cjson_skill = cJSON_CreateArray();
cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "C" ));
cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Java" ));
cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Python" ));
cJSON_AddItemToObject(cjson_test, "skill", cjson_skill);
/* 添加一个值为 False 的布尔类型的JSON数据(添加一个链表节点) */
cJSON_AddFalseToObject(cjson_test, "student");
/* 打印JSON对象(整条链表)的所有数据 */
str = cJSON_Print(cjson_test);
printf("%s\\n", str);
return 0;
编译运行:
gcc cJSON.c example1.c -o example1
实验结果如图:
该JSON数据链表的关系如图:
4.cJSON数据解析
解析方法
解析JSON数据的过程,其实就是剥离一个一个链表节点(键值对)的过程。
解析方法如下:
① 创建链表头指针:
cJSON* cjson_test = NULL;
② 解析整段JSON数据,并将链表头结点地址返回,赋值给头指针:
解析整段数据使用的API只有一个:
(cJSON *) cJSON_Parse(const char *value);
③ 根据键值对的名称从链表中取出对应的值,返回该键值对(链表节点)的地址
(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
④ 如果JSON数据的值是数组,使用下面的两个API提取数据:
(int) cJSON_GetArraySize(const cJSON *array);
(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
解析示例
下面用一个例子来说明如何解析出开头给出的那段JSON数据:
#include <stdio.h>
#include "cJSON.h"
char *message = //json格式数据,需要显示引号,都需要用\\转义
" \\
\\"name\\":\\"mculover666\\", \\
\\"age\\": 22, \\
\\"weight\\": 55.5, \\
\\"address\\": \\
\\
\\"country\\": \\"China\\",\\
\\"zip-code\\": 111111\\
, \\
\\"skill\\": [\\"c\\", \\"Java\\", \\"Python\\"],\\
\\"student\\": false \\
";
int main(void)
cJSON* cjson_test = NULL;
cJSON* cjson_name = NULL;
cJSON* cjson_age = NULL;
cJSON* cjson_weight = NULL;
cJSON* cjson_address = NULL;
cJSON* cjson_address_country = NULL;
cJSON* cjson_address_zipcode = NULL;
cJSON* cjson_skill = NULL;
cJSON* cjson_student = NULL;
int skill_array_size = 0, i = 0;
cJSON* cjson_skill_item = NULL;
/* 解析整段JSO数据 */
cjson_test = cJSON_Parse(message);
if(cjson_test == NULL)
printf("parse fail.\\n");
return -1;
/* 依次根据名称提取JSON数据(键值对) */
cjson_name = cJSON_GetObjectItem(cjson_test, "name");
cjson_age = cJSON_GetObjectItem(cjson_test, "age");
cjson_weight = cJSON_GetObjectItem(cjson_test, "weight");
printf("name: %s\\n", cjson_name->valuestring);
printf("age:%d\\n", cjson_age->valueint);
printf("weight:%.1f\\n", cjson_weight->valuedouble);
/* 解析嵌套json数据 */
cjson_address = cJSON_GetObjectItem(cjson_test, "address");
cjson_address_country = cJSON_GetObjectItem(cjson_address, "country");
cjson_address_zipcode = cJSON_GetObjectItem(cjson_address, "zip-code");
printf("address-country:%s\\naddress-zipcode:%d\\n", cjson_address_country->valuestring, cjson_address_zipcode->valueint);
/* 解析数组 */
cjson_skill = cJSON_GetObjectItem(cjson_test, "skill");
skill_array_size = cJSON_GetArraySize(cjson_skill);
printf("skill:[");
for(i = 0; i < skill_array_size; i++)
cjson_skill_item = cJSON_GetArrayItem(cjson_skill, i);
printf("%s,", cjson_skill_item->valuestring);
printf("\\b]\\n");
/* 解析布尔型数据 */
cjson_student = cJSON_GetObjectItem(cjson_test, "student");
if(cjson_student->valueint == 0)
printf("student: false\\n");
else
printf("student:error\\n");
return 0;
编译:
gcc cJSON.c example2.c -o example2
运行结果如图:
注意事项
在本示例中,因为我提前知道数据的类型,比如字符型或者浮点型,所以我直接使用指针指向对应的数据域提取,在实际使用时,如果提前不确定数据类型,应该先判断type的值,确定数据类型,再从对应的数据域中提取数据。
5.cJSON使用过程中的内存问题
内存及时释放
cJSON的所有操作都是基于链表的,所以cJSON在使用过程中大量的使用malloc从堆中分配动态内存的,所以在使用完之后,应当及时调用下面的函数,清空cJSON指针所指向的内存,该函数也可用于删除某一条数据:
(void) cJSON_Delete(cJSON *item);
注意:该函数删除一条JSON数据时,如果有嵌套,会连带删除。
内存钩子
cJSON在支持自定义malloc函数和free函数,方法如下:
① 使用cJSON_Hooks来连接自定义malloc函数和free函数:
typedef struct cJSON_Hooks
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
cJSON_Hooks;
② 初始化钩子cJSON_Hooks
(void) cJSON_InitHooks(cJSON_Hooks* hooks);
cJSON在STM32移植和使用
参照:物联网项目设计(四)cJSON 在 STM32 移植和使用
作者:张竞豪
地址:https://blog.csdn.net/weixin_42487906/article/details/104542529
概要
JSON格式是互联网通讯过程中常用的格式。包括MQTT协议也不例外,MQTT协议的数据收发都是使用JSON格式完成的。在使用MQTT协议进行通讯之前,我们需要掌握在c语言环境下JSON格式数据的操作。对JSON数据的操作我们使用了cJSON开源库。在使用的过程中,也遇到了不少问题(见后文),为此我查阅了很多资料,最后找到比较简单的方法解决了这个问题。
本篇介绍如何生成cJSON格式数据,并转化成字符串,通过串口发送到上位机。
硬件准备
使用STM32主控的开发板或相关硬件设备
软件准备
上位机串口助手
实际操作步骤
1.使用CubeMx建立一个简单的工程,只需要配置串口,但一定要注意,配置堆栈大小的时候一定要配置的大一点,因为cJSON分配内存的时候会占用不少内存空间,内存分配是单片机移植cJSON遇到的最常见的问题,如果内存分配的不对,很容易就导致生成的JSON字符串里面什么都没有。网上很多解决方案是自行编写内存管理函数,也就是malloc和free,这种方法太麻烦而且可移植性不高,最后我选择了使用增大堆栈的方式。
2.从github上获取cJSON的源码并解压,如下图
我们只需要其中的cJSON.c和cJSON.h,并把他们复制到工程中。
3.实际编写代码部分,我们需要生成一个JSON格式的字符串如下
"name": "Alpha",
"address":
"conutry": "China",
"city": "Luoyang"
,
"info":
"age": 21,
"weight": 66
实际编写代码部分,我们要注意的是,在使用cJSON之后,要及时将其删除,其中删除父对象也会自动迭代删除子对象
下面是初始化部分,完成生成JSON格式数据,并将其转化为字符串格式数据,最后删除
char *buffer;
cJSON* cjson_test = NULL;
cJSON* cjson_address = NULL;
cJSON* cjson_info = NULL;
cjson_test = cJSON_CreateObject();
//
//
cJSON_AddStringToObject(cjson_test,"name","Alpha");
cjson_address = cJSON_CreateObject();
cJSON_AddStringToObject(cjson_address,"conutry","China");
cJSON_AddStringToObject(cjson_address,"city","Luoyang");
cJSON_AddItemToObject(cjson_test,"address",cjson_address);
cjson_info = cJSON_CreateObject();
cJSON_AddNumberToObject(cjson_info,"age",21);
cJSON_AddNumberToObject(cjson_info,"weight",66);
cJSON_AddItemToObject(cjson_test,"info",cjson_info);
buffer = cJSON_Print(cjson_test);
cJSON_Delete(cjson_test);
下面是发送字符串到上位机部分
/* USER CODE BEGIN WHILE */
while (1)
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_Delay(1000);
u1_printf("You Json Is:%s\\r\\n",buffer);
/* USER CODE END 3 */
4,编译代码并在上位机中观察现象
完成了所需要的功能
总结
本部分内容完成了cJSON的移植和JSON格式的初步使用,下一阶段准备使用paho 的开源mqtt库链接百度云天工,并且使用paho自带的ping函数,完成stm32链接百度云天工后的连接检测。
快速生成JSON数据(sprintf)和解析JSON数据(strstr)技巧
参照:骚操作!快速创建JSON数据和解析JSON数据
作者:Mculover666
地址:https://mculover666.blog.csdn.net/article/details/103794279?spm=1001.2014.3001.5502
乐鑫云平台在对接的时候,可以使用tcp socket发送和接收json数据进行交互,之前专门写了一篇文章讲述cJSON的使用,然而,看了乐鑫给的官方代码后,我只能说,突如其来的骚,闪了**的腰!
1. 生成JSON数据
核心思想:构造字符串!
这里我拿设备激活举个例子,设备激活的数据格式如下:
"path": "/v1/device/activate/", "method": "POST", "meta": "Authorization": "token HERE_IS_THE_MASTER_DEVICE_KEY", "body": "encrypt_method": "PLAIN", "bssid": ":bssid", "token": ":token"
其中:
- HERE_IS_THE_MASTER_DEVICE_KEY:填写之前获取的秘钥;
- bssid:填写设备MAC地址;
- token :随机值即可,可以留空;
这样的一条数据,如果使用cJSON的函数一条一条创建,不仅代码量大,而且内存占用量也极大,接下来向大家介绍一个骚操作,看完后,我相信你会点赞的!
首先直接使用宏定义将整段JSON字符串给出:
#define ACTIVE_DATA \\
"\\n\\"path\\": \\"/v1/device/activate/\\",\\n\\
\\"method\\": \\"POST\\",\\n\\
\\"meta\\":\\
\\"Authorization\\": \\"token %s\\",\\n\\
\\"body\\":\\
\\"encrypt_method\\": \\"PLAIN\\",\\
\\"bssid\\": \\"%s\\",\\
\\"token\\": \\":\\"\\n\\n"
然后一行命令即可构造出将其中两个需要用户给出的值定义:
#define TOKEN "ef97b306620d5e9de19d7b2131742b152b2e94d0"
#define BSSID "dc:4f:22:5e:90:b7"
最后一行代码解决问题,简洁到爆炸:使用拼接函数
sprintf(active_data, ACTIVE_DATA, TOKEN, BSSID);
整段程序如下:
#include <stdio.h>
#include <stdlib.h>
#define ACTIVE_DATA \\
"\\n\\"path\\": \\"/v1/device/activate/\\",\\n\\
\\"method\\": \\"POST\\",\\n\\
\\"meta\\":\\
\\"Authorization\\": \\"token %s\\",\\n\\
\\"body\\":\\
\\"encrypt_method\\": \\"PLAIN\\",\\
\\"bssid\\": \\"%s\\",\\
\\"token\\": \\":\\"\\n\\n"
#define TOKEN "ef97b306620d5e9de19d7b2131742b152b2e94d0"
#define BSSID "dc:4f:22:5e:90:b7"
int main(void)
char* active_data = malloc(300);
sprintf(active_data, ACTIVE_DATA, TOKEN, BSSID);
printf(active_data);
return 0;
编译运行:
2. 解析JSON数据
"deliver_to_device": true, "get": "action": "LED_ON", "meta": "Authorization": "token 8668c64f40d172be31eb4f12cbcaf4e57c9e1e3c", "Time-Zone": "Asia/Shanghai", "method": "GET", "nonce": 587250592, "path": C/C++ 使用cjson库 操作Json格式文件(创建插入解析修改删除)