当前位置: 首页 > news >正文

第二章 EXI协议原理与实现--9.5 cjson库介绍

9.5 cjson库介绍

前面使用了cjson库作为格式化字符串解析工具,本节简单介绍下cjson库的使用方法,下节介绍发现的问题:对长整型数据无法解析,作者提出了一种解决方法。 
【参考资料】
cJSON源码及解析流程详解-CSDN博客
这篇文章分析的挺到位的,对代码进行解读和注释,非常用心的笔记,感谢作者Tyler_Zx。
cJSON使用详细教程 | 一个轻量级C语言JSON解析器-CSDN博客
cJSON源码解析(超级详细!!!)-CSDN博客
CJSON源码研究笔记_cjson valuedouble-CSDN博客
cJSON详细剖析(一)----框架_cjson软件架构-CSDN博客
cJSON是一个使用C语言编写的JSON数据解析器,具有超轻便,可移植,单文件的特点,使用MIT开源协议。
cJSON官网下载: https://sourceforge.net/projects/cjson/
cJSON项目托管在Github上,仓库地址如下: GitHub - DaveGamble/cJSON: Ultralightweight JSON parser in ANSI C
其中cJSON的源码文件只有两个,使用的时候,只需要将这两个文件复制到工程目录,然后包含头文件cJSON.h即可。
* cJSON.h
* cJSON.c

9.5.1 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指针指向该条新链表。
要特别注意child指针,但凡是嵌套的子对象或数组,都会存放到child链表中。 示例:
指针关系图:
9.5.2 JSON数据封装
数据封装表示如何填写cJSON数据,构造出完整的sJSON对象。
我们开始讲述创建一段完整的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);

具体封装示例代码:

#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);
    free(str);

   //释放json对象内存
   cJSON_Delete(cjson_test);

    return 0;
}

输出JSON数据

上面讲述,一段完整的JSON数据就是一条长长的链表,那么,如何打印出这段JSON数据呢?
cJSON提供了一个API,可以将整条链表中存放的JSON信息输出到一个字符串中,
(char *) cJSON_Print(const cJSON *item);
 在该函数内部由malloc分配了一段内存,然后返回字符串指针。使用的时候,需要接收该函数返回的指针地址, 打印使用之后必须手动删除该指针,否则就会出现内存泄漏。 用法示例:
    char * pp = cJSON_Print(cjson);
    printf("%s\n\n", pp);
    free(pp);

释放JSON数据

使用cJSON_CreateObject创建的cjson对象是由内部使用malloc在堆上分配的内存空间,因此在使用后必须手动释放内存。由于cjson具备列表结构,在删除根节点时就会递归删除之后的节点和子节点,也就完成了内存空间的释放。 因此,我们只要调用删除根节点就可以完全释放内存了。

9.5.3 解析数据

解析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 =
"{                              \
    \"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;
}

运行结果如图:

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

相关文章:

  • NLP高频面试题(十四)——DPO、PPO等强化学习训练方法介绍
  • 【Altium Designer】铜皮编辑
  • 信息安全和病毒防护——安全协议关于SSL和TLS协议的补充说明
  • 【AVRCP】GOEP互操作性深度解析:蓝牙封面艺术传输的技术实现与演进
  • 数据结构八股
  • PRC框架(以Dubbo为例),分布式事务解决方案
  • React(四)setState原理-性能优化-ref
  • GPT-4 and ChatGPT Essentials
  • 关于“会议视频人脸情绪识别系统”的功能设计方案示例
  • 深入理解C语言数据结构之快速排序三路划分
  • MQ 消息持久化方案
  • Android 静态壁纸设置实现方案
  • 应用服务接口第二次请求一直pending问题
  • 网络故障排查
  • C++学习之路,从0到精通的征途:string类
  • 23种设计模式中的策略模式
  • 深入解析 Spring 启动过程
  • 借助可视化,快速洞察数据背后的商机
  • 地理信息系统(GIS)在智慧城市中的40个应用场景案例
  • JUC并发编程
  • 湖南湘西州副州长刘冬生主动交代问题,接受审查调查
  • 河南洛阳新安县煤渣倾倒耕地:多年难恢复,为何至今未解决?
  • 拿出压箱底作品,北京交响乐团让上海观众享受音乐盛宴
  • 玉渊谭天丨一艘航母看中国稀土出口管制为何有效
  • 印度杰纳布河上游两座水电站均已重新开闸
  • 中信银行:拟出资100亿元全资设立信银金融资产投资有限公司