发 帖  
原厂入驻New
LiteOS云端对接教程01-cJSON组件使用教程
2020-2-26 09:53:02  404 单片机 物联网 小熊派BearPi 华为云 Liteos
分享
本帖最后由 小熊派开源社区 于 2020-2-26 09:58 编辑

1. JSON与cJSON

JSON —— 轻量级的数据格式

JSON 全称 JavaScript Object Notation,即 JS对象简谱,是一种轻量级的数据格式。
它采用完全独立于编程语言的文本格式来存储和表示数据,语法简洁、层次结构清晰,易于人阅读和编写,同时也易于机器解析和生成,有效的提升了网络传输效率。

JSON语法规则

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

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

示例如下:
  1. {
  2.     "name": "mculover666",
  3.     "age": 22,
  4.     "weight": 55.5
  5.     "address":
  6.     {
  7.         "country": "China",
  8.         "zip-code": 111111
  9.     },
  10.     "skill": ["c", "Java", "Python"],
  11.     "student": false
  12. }
复制代码

LiteOS中的cJSON组件

cJSON是一个使用C语言编写的JSON数据解析器,具有超轻便,可移植,单文件的特点,使用MIT开源协议。
LiteOS中已经移植了cJSON,作为一个组件使用,源码在sdk\IoT_LINK_1.0.0\iot_link\cJSON中,其源码文件只有两个:



  • 使用的时候,只需要将这两个文件复制到工程目录,然后包含头文件cJSON.h即可,如下:
    1. #include "cJSON.h"
    复制代码

    2. cJSON数据结构和设计思想

    cJSON的设计思想从其数据结构上就能反映出来。
    cJSON使用cJSON结构体来表示一个JSON数据,定义在cJSON.h中,源码如下:
    1. /* The cJSON structure: */

    2. typedef struct cJSON
    3. {
    4.     /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
    5.     struct cJSON *next;
    6.     struct cJSON *prev;
    7.     /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
    8.     struct cJSON *child;

    9.     /* The type of the item, as above. */
    10.     int type;

    11.     /* The item's string, IF type==cJSON_String  and type == cJSON_Raw */
    12.     char *valuestring;
    13.     /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
    14.     int valueint;
    15.     /* The item's number, if type==cJSON_Number */
    16.     double valuedouble;

    17.     /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
    18.     char *string;
    19. } 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申请内存的方式变为使用osal_malloc申请,自动初始化代码在link_main.c文件中:
      

    cJSON组件自动初始化代码

    4. JSON数据封装

    封装方法
    封装JSON数据的过程,其实就是创建链表和向链表中添加节点的过程。
    首先来讲述一下链表中的一些术语:
    • 头指针:指向链表头结点的指针;
    • 头结点:不存放有效数据,方便链表操作;
    • 首节点:第一个存放有效数据的节点;
    • 尾节点:最后一个存放有效数据的节点;

    明白了这几个概念之后,我们开始讲述创建一段完整的JSON数据,即如何创建一条完整的链表。
    • ① 创建头指针:
    1. cJSON* cjson_test = NULL;
    复制代码
    • ② 创建头结点,并将头指针指向头结点:
    1. cjson_test = cJSON_CreateObject();
    复制代码
    • ③ 尽情的向链表中添加节点:
    1. cJSON_AddNullToObject(cJSON * const object, const char * const name);

    2. cJSON_AddTrueToObject(cJSON * const object, const char * const name);

    3. cJSON_AddFalseToObject(cJSON * const object, const char * const name);

    4. cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);

    5. cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);

    6. cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);

    7. cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);

    8. cJSON_AddObjectToObject(cJSON * const object, const char * const name);

    9. cJSON_AddArrayToObject(cJSON * const object, const char * const name);
    复制代码
    输出JSON数据
    上面讲述,一段完整的JSON数据就是一条长长的链表,那么,如何打印出这段JSON数据呢?
    cJSON提供了一个API,可以将整条链表中存放的JSON信息输出到一个字符串中:
    1. (char *) cJSON_Print(const cJSON *item);
    复制代码
    使用的时候,只需要接收该函数返回的指针地址即可。

    封装数据和打印数据示例
    单纯的讲述方法还不够,下面用一个例子来说明,封装出开头给出的那段JSON数据。
    首先基于HelloWorld工程,创建一个存放示例文件的文件夹cloud_test_demo,并新建一个实验文件cjson_print_demo.c,编写如下代码:
    1. #include <osal.h>
    2. #include <stdio.h>
    3. #include <cJSON.h>

    4. static int cjson_print_demo_entry()
    5. {
    6.     cJSON* cjson_test = NULL;
    7.     cJSON* cjson_address = NULL;
    8.     cJSON* cjson_skill = NULL;
    9.     char* str = NULL;

    10.     /* 创建一个JSON数据对象(链表头结点) */
    11.     cjson_test = cJSON_CreateObject();

    12.     /* 添加一条字符串类型的JSON数据(添加一个链表节点) */
    13.     cJSON_AddStringToObject(cjson_test, "name", "mculover666");

    14.     /* 添加一条整数类型的JSON数据(添加一个链表节点) */
    15.     cJSON_AddNumberToObject(cjson_test, "age", 22);

    16.     /* 添加一条浮点类型的JSON数据(添加一个链表节点) */
    17.     cJSON_AddNumberToObject(cjson_test, "weight", 55.5);

    18.     /* 添加一个嵌套的JSON数据(添加一个链表节点) */
    19.     cjson_address = cJSON_CreateObject();
    20.     cJSON_AddStringToObject(cjson_address, "country", "China");
    21.     cJSON_AddNumberToObject(cjson_address, "zip-code", 111111);
    22.     cJSON_AddItemToObject(cjson_test, "address", cjson_address);

    23.     /* 添加一个数组类型的JSON数据(添加一个链表节点) */
    24.     cjson_skill = cJSON_CreateArray();
    25.     cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "C" ));
    26.     cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Java" ));
    27.     cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Python" ));
    28.     cJSON_AddItemToObject(cjson_test, "skill", cjson_skill);

    29.     /* 添加一个值为 False 的布尔类型的JSON数据(添加一个链表节点) */
    30.     cJSON_AddFalseToObject(cjson_test, "student");

    31.     /* 打印JSON对象(整条链表)的所有数据 */
    32.     str = cJSON_Print(cjson_test);
    33.     printf("%s\n", str);

    34.     /* 释放整条链表内存 */
    35.     cJSON_Delete(cjson_test);

    36.     return 0;
    37. }

    38. int standard_app_demo_main()
    39. {
    40.     osal_task_create("cjson_print_demo",cjson_print_demo_entry,NULL,0x800,NULL,2);
    41.     return 0;
    42. }
    复制代码
    在user_demo.mk中配置文件路径:
    1.     #example for cjson_print_demo
    2.     ifeq ($(CONFIG_USER_DEMO), "cjson_print_demo")  
    3.         user_demo_src  = ${wildcard $(TOP_DIR)/targets/STM32L431_BearPi/Demos/cloud_test_demo/cjson_print_demo.c}
    4.     endif
    复制代码
    位置如下:
      
    然后在.sdkconfig中开启cJSON组件,并且选中该demo:
      

    实验结果如图:
      


    5. cJSON数据解析
    解析方法
    解析JSON数据的过程,其实就是剥离一个一个链表节点(键值对)的过程。
    解析方法如下:
    • ① 创建链表头指针:
    1. cJSON* cjson_test = NULL;
    复制代码
    • ② 解析整段JSON数据,并将链表头结点地址返回,赋值给头指针:
    解析整段数据使用的API只有一个:
    1. (cJSON *) cJSON_Parse(const char *value);
    复制代码
    • ③ 根据键值对的名称从链表中取出对应的值,返回该键值对(链表节点)的地址
    1. (cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
    复制代码
    • ④ 如果JSON数据的值是数组,使用下面的两个API提取数据:
    1. (int) cJSON_GetArraySize(const cJSON *array);
    2. (cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
    复制代码
    解析示例
    下面用一个例子来说明如何解析出开头给出的那段JSON数据。
    在存放示例文件的文件夹cloud_test_demo中,再新建一个实验文件cjson_parse_demo.c,编写如下代码:
    1. #include <osal.h>
    2. #include <stdio.h>
    3. #include <cJSON.h>

    4. char *message =
    5. "{                              \
    6.     \"name\":\"mculover666\",   \
    7.     \"age\": 22,                \
    8.     \"weight\": 55.5,           \
    9.     \"address\":                \
    10.         {                       \
    11.             \"country\": \"China\",\
    12.             \"zip-code\": 111111\
    13.         },  \
    14.     \"skill\": [\"c\", \"Java\", \"Python\"],\
    15.     \"student\": false\
    16. }";

    17. static int cjson_test1_demo_entry()
    18. {
    19.    cJSON* cjson_test = NULL;
    20.     cJSON* cjson_name = NULL;
    21.     cJSON* cjson_age = NULL;
    22.     cJSON* cjson_weight = NULL;
    23.     cJSON* cjson_address = NULL;
    24.     cJSON* cjson_address_country = NULL;
    25.     cJSON* cjson_address_zipcode = NULL;
    26.     cJSON* cjson_skill = NULL;
    27.     cJSON* cjson_student = NULL;
    28.     int    skill_array_size = 0, i = 0;
    29.     cJSON* cjson_skill_item = NULL;

    30.     /* 解析整段JSO数据 */
    31.     cjson_test = cJSON_Parse(message);
    32.     if(cjson_test == NULL)
    33.     {
    34.         printf("parse fail.\n");
    35.         return -1;
    36.     }

    37.     /* 依次根据名称提取JSON数据(键值对) */
    38.     cjson_name = cJSON_GetObjectItem(cjson_test, "name");
    39.     cjson_age = cJSON_GetObjectItem(cjson_test, "age");
    40.     cjson_weight = cJSON_GetObjectItem(cjson_test, "weight");

    41.     printf("name: %s\n", cjson_name->valuestring);
    42.     printf("age:%d\n", cjson_age->valueint);
    43.     printf("weight:%.1f\n", cjson_weight->valuedouble);

    44.     /* 解析嵌套json数据 */
    45.     cjson_address = cJSON_GetObjectItem(cjson_test, "address");
    46.     cjson_address_country = cJSON_GetObjectItem(cjson_address, "country");
    47.     cjson_address_zipcode = cJSON_GetObjectItem(cjson_address, "zip-code");
    48.     printf("address-country:%s\naddress-zipcode:%d\n", cjson_address_country->valuestring, cjson_address_zipcode->valueint);

    49.     /* 解析数组 */
    50.     cjson_skill = cJSON_GetObjectItem(cjson_test, "skill");
    51.     skill_array_size = cJSON_GetArraySize(cjson_skill);
    52.     printf("skill:[");
    53.     for(i = 0; i < skill_array_size; i++)
    54.     {
    55.         cjson_skill_item = cJSON_GetArrayItem(cjson_skill, i);
    56.         printf("%s,", cjson_skill_item->valuestring);
    57.     }
    58.     printf("\b]\n");

    59.     /* 解析布尔型数据 */
    60.     cjson_student = cJSON_GetObjectItem(cjson_test, "student");
    61.     if(cjson_student->valueint == 0)
    62.     {
    63.         printf("student: false\n");
    64.     }
    65.     else
    66.     {
    67.         printf("student:error\n");
    68.     }

    69.     /* 释放整条链表内存 */
    70.     cJSON_Delete(cjson_test);
    71.    
    72.     return 0;
    73. }

    74. int standard_app_demo_main()
    75. {
    76.     osal_task_create("cjson_test1_demo",cjson_test1_demo_entry,NULL,0x800,NULL,2);
    77.     return 0;
    78. }
    复制代码
    在user_demo.mk中配置文件路径:
    1.     #example for cjson_parse_demo
    2.     ifeq ($(CONFIG_USER_DEMO), "cjson_parse_demo")  
    3.         user_demo_src  = ${wildcard $(TOP_DIR)/targets/STM32L431_BearPi/Demos/cloud_test_demo/cjson_parse_demo.c}
    4.     endif
    复制代码
    位置如下:
      


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


    实验结果如图:
      


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

    6. cJSON使用过程中的内存问题

    内存及时释放
    cJSON的所有操作都是基于链表的,所以cJSON在使用过程中大量的使用malloc从堆中分配动态内存的,所以在使用完之后,应当及时调用下面的函数,清空cJSON指针所指向的内存,该函数也可用于删除某一条数据:
    1. (void) cJSON_Delete(cJSON *item);
    复制代码

    注意:该函数删除一条JSON数据时,如果有嵌套,会连带删除。

    内存钩子
    cJSON在支持自定义malloc函数和free函数,方法如下:
    • ① 使用cJSON_Hooks来连接自定义malloc函数和free函数:
    1. typedef struct cJSON_Hooks
    2. {
    3.       /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
    4.       void *(CJSON_CDECL *malloc_fn)(size_t sz);
    5.       void (CJSON_CDECL *free_fn)(void *ptr);
    6. } cJSON_Hooks;
    复制代码
    • ② 初始化钩子cJSON_Hooks
    1. (void) cJSON_InitHooks(cJSON_Hooks* hooks);
    复制代码


0
分享淘帖 显示全部楼层

评论

高级模式
您需要登录后才可以回帖 登录 | 注册

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
发资料
关闭

站长推荐 上一条 /7 下一条

快速回复 返回顶部 返回列表