1. JSON与cJSON

JSON —— 轻量级的数据格式

JSON 全称 JavaScript Object Notation,即 JS对象简谱,是一种轻量级的数据格式。

它采用完全独立于编程语言的文本格式来存储和表示数据,语法简洁、层次结构清晰,易于人阅读和编写,同时也易于机器解析和生成,有效的提升了网络传输效率。

JSON语法规则

JSON对象是一个无序的"名称/值"键值对的集合:

  • 以"{"开始,以"}"结束,允许嵌套使用;
  • 每个名称和值成对出现,名称和值之间使用":"分隔;
  • 键值对之间用","分隔
  • 在这些字符前后允许存在无意义的空白符;

对于键值,可以有如下值:

  • 一个新的json对象
  • 数组:使用"["和"]"表示
  • 数字:直接表示,可以是整数,也可以是浮点数
  • 字符串:使用引号"表示
  • 字面值:false、null、true中的一个(必须是小写)

示例如下:

{"name": "mculover666","age": 22,"weight": 55.5"address":{"country": "China","zip-code": 111111},"skill": ["c", "Java", "Python"],"student": false
}

LiteOS中的cJSON组件

cJSON是一个使用C语言编写的JSON数据解析器,具有超轻便,可移植,单文件的特点,使用MIT开源协议。

LiteOS中已经移植了cJSON,作为一个组件使用,源码在sdkIoT_LINK_1.0.0iot_linkcJSON中,其源码文件只有两个:

  • 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. 开启cJSON组件

在LiteOS中,cJSON组件默认是未开启的,使用宏定义CONFIG_JSON_ENABLE开启。

开启之后,LiteOS会自动进行初始化,并且使用cJSON的内存钩子将cJSON申请内存的方式变为使用osalmalloc申请,自动初始化代码在`linkmain.c`文件中:

4. 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数据。

首先基于HelloWorld工程,创建一个存放示例文件的文件夹cloud_test_demo,并新建一个实验文件cjson_print_demo.c,编写如下代码:

#include <osal.h>
#include <stdio.h>
#include <cJSON.h>static int cjson_print_demo_entry()
{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("%sn", str);/* 释放整条链表内存 */cJSON_Delete(cjson_test);return 0;
}int standard_app_demo_main()
{osal_task_create("cjson_print_demo",cjson_print_demo_entry,NULL,0x800,NULL,2);return 0;
}

在user_demo.mk中配置文件路径:

    #example for cjson_print_demoifeq ($(CONFIG_USER_DEMO), "cjson_print_demo")    user_demo_src  = ${wildcard $(TOP_DIR)/targets/STM32L431_BearPi/Demos/cloud_test_demo/cjson_print_demo.c}endif

位置如下:

然后在.sdkconfig中开启cJSON组件,并且选中该demo:

实验结果如图:

5. 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数据。

在存放示例文件的文件夹cloud_test_demo中,再新建一个实验文件cjson_parse_demo.c,编写如下代码:

#include <osal.h>
#include <stdio.h>
#include <cJSON.h>char *message =
"{                              "name":"mculover666",   "age": 22,                "weight": 55.5,           "address":                {                       "country": "China","zip-code": 111111},  "skill": ["c", "Java", "Python"],"student": false
}";static int cjson_test1_demo_entry()
{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: %sn", cjson_name->valuestring);printf("age:%dn", cjson_age->valueint);printf("weight:%.1fn", 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:%snaddress-zipcode:%dn", 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: falsen");}else{printf("student:errorn");}/* 释放整条链表内存 */cJSON_Delete(cjson_test);return 0;
}int standard_app_demo_main()
{osal_task_create("cjson_test1_demo",cjson_test1_demo_entry,NULL,0x800,NULL,2);return 0;
}

在user_demo.mk中配置文件路径:

    #example for cjson_parse_demoifeq ($(CONFIG_USER_DEMO), "cjson_parse_demo")    user_demo_src  = ${wildcard $(TOP_DIR)/targets/STM32L431_BearPi/Demos/cloud_test_demo/cjson_parse_demo.c}endif

位置如下:

然后在.sdkconfig中开启cJSON组件,并且选中该demo:

实验结果如图:

注意事项

在本示例中,因为我提前知道数据的类型,比如字符型或者浮点型,所以我直接使用指针指向对应的数据域提取,在实际使用时,如果提前不确定数据类型,应该先判断type的值,确定数据类型,再从对应的数据域中提取数据

6. 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使用_LiteOS云端对接教程01-cJSON组件使用教程相关推荐

  1. primefaces教程_Primefaces BlockUI组件示例教程

    primefaces教程 Primefaces BlockUI is used to block interactivity of JSF components with optional ajax ...

  2. primefaces教程_Primefaces FileUpload组件示例教程

    primefaces教程 Today we will look into the Primefaces FileUpload component. HTML provides you file inp ...

  3. primefaces教程_Primefaces仪表板组件示例教程

    primefaces教程 We've mentioned earlier, Primefaces is one of leading libraries that provide you set of ...

  4. primefaces教程_Primefaces日历组件示例教程

    primefaces教程 In our previous tutorials, we've covered several types of Primefaces components such as ...

  5. 原创教程之——reactjs 组件入门教程

    在学习react之前,希望你有以下准备: react的安装 ECMAScript 6基础 本文不讲解react的安装步骤,若需了解请移步官方网站(https://reactjs.org/),那里讲解非 ...

  6. 物联网平台搭建教程01

    物联网平台搭建教程01 1 物联网设备如何接入到网络? 2 网络通信方式 3 物联网设备之间,设备与云平台能够交换数据后,接下来要干怎么呢? 4 如何搭建起一个物联网系统框架呢?它的技术架构又是怎么样 ...

  7. QIIME 2教程. 01简介和安装 Introduction Install(2020.11开始更新)

    写在前面 QIIME是微生物组领域最广泛使用的分析流程,10年来引用20000+次,2019年Nature杂志评为近70年来人体菌群研究的25个里程碑事件--里程碑16:生物信息学工具助力菌群测序数据 ...

  8. H2O Wave教程---基于浏览器的实时显示工具---教程01

    H2O Wave教程-基于浏览器的实时显示工具-教程01 0 写在前面 1 开始-动手操作起来 2 分类:一个是脚本,一个是app 3 脚本怎么写 0 写在前面 总结一下自己学习H2Owave的学习情 ...

  9. PHPwind9.01傻瓜图解安装教程

    PHPwind9.01傻瓜图解安装教程 大家好,按照惯例,PHPwind出新版本,我就会习惯发布图解安装教程,现在开始了~  首先,     1.您的空间要支持php+mysql,     2.您的m ...

最新文章

  1. android程序安装后图标不显示
  2. BCH压力测试悄然开始?有优势但也有不足!
  3. iOS学习资源(三)
  4. 电脑常见故障处理_关于密封仪、密封试验仪器在使用上的常见故障及维护方面...
  5. Centos7 yum安装Python3.6环境,超简单
  6. [BUUCTF-pwn]——picoctf_2018_echo back
  7. python类的空间问题及类之间的关系
  8. exp导出表结构,不导出表数据。
  9. Boost组件lexical_cast
  10. Java的throws Exception
  11. eo是什么?一份没有满分的试题
  12. B2B供应链管理平台主流技术架构方案
  13. 惯性导航技术, IMU, AHRS
  14. c语言打印星号金字塔图形
  15. html中背景简写,css中background简写属性
  16. OneDrive教育版注册和登录
  17. IBM X3530 M4 RAID 卡驱动下载
  18. 路由器经常断网,网络不稳定怎么办, 教你如何让旧路由器也能运转飞起
  19. 安装cartographer出现的问题及解决方法
  20. WebView 生成长图,截图

热门文章

  1. python中字符串的几种表达方式(用什么方式表示字符串)
  2. Python 中的高级斗技,让函数返回结果的技巧
  3. 用 Python 快速实现 HTTP 和 FTP 服务器
  4. java创建阻塞_如何从HttpsURLConnection创建Java非阻塞InputStream?
  5. 深度学习中batch_size、epoch和iteration的含义
  6. python opencv cv2.resize()函数
  7. python 原始字符串r的用法
  8. 为什么不使用volatile,其它线程也能得到当前线程修改后的值,不使用volatile也不存在可见性问题?原来解决可见性问题不一定需要volatile,println也可以
  9. 变异蛮牛 树,dfs,二分图染色 牛客白月赛44
  10. rust编程之道 pdf_LPC: 想在内核里引入Rust,还需要做很多决定