• cJson 学习笔记


    cJson 学习笔记

    一、前言

    思考这么一个问题:对于不同的设备如何进行数据交换?可以考虑使用轻量级别的 JSON 格式。

    那么需要我们手写一个 JSON 解析器吗?这大可不必,因为已经有前辈提供了开源的轻量级的 JSON 解析器——cJSON。我们会用就可以了,当然你也可以深入源码进行学习。

    下图则向我们展示了如何通过 cJSON 实现 Client 与 Server 的数据交换:

    image-20221119161207712

    • Client 在发送数据之前,通过 cJSON 将自己的专属数据格式 Data_ClientFormat 转化为了通用格式 JSON
    • 服务端在收到 JSON 数据后,通过 cJSON 将其转化为服务端的专属数据格式 Data_ServerFormat
    • 反之同理

    在介绍 cJSON 之前,先来对 JSON 这个数据格式有个简单了解。

    二、JSON 简介

    1.1 什么是 JSON

    JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation)。但它并不是编程语言,而是一种可以在服务器和客户端之间传输的数据交换格式

    1.2 JSON 结构

    JSON 的两种数据结构:

    1. 对象:A collection of key/value pairs(一个无序的 key / value 对的集合)
    2. 数组:An ordered list of values(一 / 多个 value 的有序列表)

    从上述描述中,我们可以获得如下四种信息:

    • 对象(Object)
    • 数组(Array)
    • 键(key)
    • 值(Value)

    1.3 JSON 对象

    JSON 对象具体格式如下图所示:

    image-20221118232125879

    • 一个对象以{开始,以}结束,是若干「key / value 对」的集合
    • key 和 value 使用:分隔
    • key / value 对之间使用,分隔

    注意事项:

    1. 键:必须是 string 类型
    2. 值:可以是合法的 JSON 数据类型(字符串、数值、对象、数组、布尔值或 null)

    如,这是一个合法的 JSON 对象:

    {
        "name" : "张三"
    }

    这也是一个合法的 JSON 对象:

    {
        "name" : "张三",
        "age"  : 18,
        "sex"  : "男"
    }

    1.4 JSON 数组

    JSON 数组具体格式如下图所示:

    image-20221118232514539

    • 一个数组以[开始,]结束,是若干 value 的有序集合
    • 多个 value 以,分隔

    如,这是一个合法的 JSON 数组:

    [
        "张三",
        18,
        "男"
    ]

    该数组包含三个 value,分别为 string、number、string

    这也是一个合法的 JSON 数组:

    [
        {
            "name"	: "张三",
            "age"	: 18,
            "sex"	: "男"
        },
        {
            "name"	: "李四",
            "age"	: 19,
            "sex"	: "男"
        }
    ]

    该数组包含两个 Object,每个 Object 又包含若干 key / value。

    1.5 JSON 值

    值(value)可以是:

    • 字符串:必须用双引号括起来
    • 数 值:十进制整数或浮点数
    • 对 象:键 / 值对的集合
    • 数 组:值的集合
    • 布尔值:true 或 false
    • null

    value 可以是简单的用双引号引起来的 string 串,也可以是一个数值;或布尔值(true or false),或 null。

    当然也可以是复杂的 object 或 array,这些取值是可以嵌套的。

    image-20221120122601710

    在「1.4 JSON 数组」中,第二个例子就是一个嵌套的举例,当然也可以这么嵌套:

    {
        "class_name"	: "计科一班",
        "student_num"	: 2,
        "student_info"	: 
        [
            {
                "name"	: "张三",
                "age"	: 18,
                "sex"	: "男"
            },
            {
                "name"	: "李四",
                "age"	: 19,
                "sex"	: "男"
            }
        ]
    }

    三、cJSON 使用教程

    3.1 cJSON 使用说明

    源码下载:https://www.aliyundrive.com/s/vms4mGLStGm

    编译环境:CentOS 7

    源码中包含 cJSON.h 和 cJSON.c,以及一个测试程序 main.c,测试程序的功能是输出一个 JSON 字符串:

    {
        "name": "张三",
        "age":  18,
        "sex":  "男"
    }

    你可以通过下面两种方法运行该程序:

    1. $ gcc *.c -o main -lm,会生成一个可执行文件 main,执行该文件即可
    2. 将 cJSON.c 打包成静态库 / 动态库,然后在编译 main.c 的时候将其链接上就可以了

    由于源码中使用了 pow、floor 函数,所以在编译的时要链接上 math 库,也就是 -lm 指令。

    如果在编译过程中报好多 warning(如下图所示)警告:

    image-20221120120313471

    不要慌,这并不影响程序的运行,如果你想消除这些警告,不妨将 cJSON.c 格式化一下(用 VSCode 按下alt+shift+F)。

    至于原理,不妨参考这篇文章:gcc编译警告关于(warning: XXX...[-Wmisleading-indentation] if(err)之类的问题)

    3.2 cJSON structure

    首先,我们先对 cJSON 的结构体有个初步了解,其定义如下:

    typedef struct cJSON
    {
      struct cJSON *next, *prev;
      struct cJSON *child;
    
      int type;
    
      char *valuestring;
      int valueint;
      double valuedouble;
    
      char *string;
    } cJSON;
    • type:用于区分 JSON 类型
      • 0 表示 false
      • 1 表示 true
      • 2 表示 null
      • 3 表示 number
      • 4 表示 string
      • 5 表示 array
      • 6 表示 object
    • string :代表「键 / 值对」的键
    • value*:代表「键 / 值对」的值,搭配 type 使用
      • 只有当 type 值为 4 时,valuestring 字段才有效
      • 只有当 type 值为 3 时,valueint 或 valuedouble 字段才有效

    由于我在实际使用过程中未涉及 bool、null,所以举例中暂不涉及这两种类型。

    3.3 反序列化 JSON 字符串

    在正式讲解之前,让我们先看一下与反序列化相关的函数:

    函数 解释说明 返回值
    cJSON_Parse 将 JSON 字符串反序列化为 cJSON 结构体 cJSON *
    cJSON_GetObjectItem 获取 JSON 对象中的指定项 cJSON *
    cJSON_GetArrayItem 获取 JSON 数组中的第 i 个 JSON 项 cJSON *
    cJSON_GetArraySize 获取 JSON 数组的大小(该数组中包含的 JSON 项个数) int
    cJSON_Delete 删除 cJSON 结构体 void

    3.3.1 一个简单的例子

    对于一个 JSON 字符串:

    {
        "name": "张三",
        "age": 18,
        "sex": "男"
    }

    我们可以在代码中通过调用cJSON_Parse(const char *value)函数将 JSON 字符串 value 反序列化为 cJSON 结构体:

    cJSON *root = cJSON_Parse(pcJson); // pcJson 为从文件中获取的 JSON 字符串
    if (NULL == root)
    {
        printf("fail to call cJSON_Parse\n");
        exit(0);
    }

    反序列化后的 JSON 字符串,大概长这个样子:

    image-20221119221819835

    • 图中的灰色虚线是假想的,实际是不存在的
    • 用来表明 name、age、sex 都是 root 的 child,只不过实际的 child 仅仅指向了第一个节点,也就是 name

    那么我们如何获取 name、age、sex 对应的值呢?可以通过调用cJSON *cJSON_GetObjectItem()函数从 object 中获取。

    cJSON *pName = cJSON_GetObjectItem(root, "name");
    printf("name [%s]\n", pName->valuestring);
    
    cJSON *pAge = cJSON_GetObjectItem(root, "age");
    printf("age  [%d]\n", pAge->valueint);
    
    cJSON *pSex = cJSON_GetObjectItem(root, "sex");
    printf("sex  [%s]\n", pSex->valuestring);
    • cJSON *cJSON_GetObjectItem(cJSON *object, const char *string):从 object 的所有 child 中检索键为 string 的 JSON 项
      • 如果找到则返回相应的 JSON 项
      • 反之返回 NULL。

    完整代码如下:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include "cJSON.h"
    
    #define STRING_LEN_MAX 2048
    
    void GetJSONFromFile(const char *FILENAME, char **ppcJson)
    {
        FILE *fp = fopen(FILENAME, "r");
        if (NULL == fp)
        {
            printf("file open error\n");
            exit(0);
        }
    
        char *pcJson = (char *)malloc(STRING_LEN_MAX);
        memset(pcJson, 0, STRING_LEN_MAX);
    
        do
        {
            fgets(pcJson + strlen(pcJson), STRING_LEN_MAX - strlen(pcJson), fp);
        } while (!feof(fp));
    
        *ppcJson = pcJson;
    
        fclose(fp);
    }
    
    int main()
    {
        char *pcJson;
    
        GetJSONFromFile("test.json", &pcJson); // 从文件 test.json 中获取 JSON 字符串
    
        cJSON *root = cJSON_Parse(pcJson);
        if (NULL == root)
        {
            printf("fail to call cJSON_Parse\n");
            exit(0);
        }
    
        cJSON *pName = cJSON_GetObjectItem(root, "name");
        printf("name [%s]\n", pName->valuestring);
    
        cJSON *pAge = cJSON_GetObjectItem(root, "age");
        printf("age  [%d]\n", pAge->valueint);
    
        cJSON *pSex = cJSON_GetObjectItem(root, "sex");
        printf("sex  [%s]\n", pSex->valuestring);
    
        cJSON_Delete(root);	// 手动调用 cJSON_Delete 进行内存回收
        
        return 0;
    }

    3.3.2 一个有一丢丢复杂的例子

    对于一个复杂些的 JSON 字符串:

    {
        "class_name": "计科一班",
        "stu_num"   : 2,
        "stu_info"  : 
        [
            {
                "name": "张三",
                "age": 18,
                "sex": "男"
            },
            {
                "name": "李四",
                "age": 20,
                "sex": "男"
            }
        ]
    }

    反序列化该字符串依旧很简单,只需我们在代码中调用cJSON_Parse()即可,而难点在于如何解析。

    先来看一下该字符串反序列化后长啥样:

    image-20221119223404283

    对于 class_name 以及 stu_name,我们可以很容易就解析出来:

    cJSON *pClassName = cJSON_GetObjectItem(root, "class_name");
    printf("class name [%s]\n", pClassName->valuestring);
    
    cJSON *pStuNum = cJSON_GetObjectItem(root, "stu_num");
    printf("stu num    [%d]\n", pStuNum->valueint);

    那么如何获取更深层次的 name、age 以及 sex 呢?

    通过 JSON 字符串可以知道,stu_info 是一个 JSON 数组,那么我们首先要做的是将这个数组从 root 中摘出来:

    cJSON *pArray = cJSON_GetObjectItem(root, "stu_info");
    if (NULL == pArray)
    {
        printf("not find stu_info\n");
        goto err;
    }

    接着将该数组中的各个项依次取出。

    cJSON *item_0 = cJSON_GetArrayItem(pArray, 0);
    cJSON *item_1 = cJSON_GetArrayItem(pArray, 1);
    • cJSON_GetArrayItem(cJSON *array, int item):从 JSON 数组 array 中获取第 item 项(下标从 0 开始)

      • 如果存在,则返回相应的 JSON 项

      • 反之返回 NULL。

    最后,将 name、age、sex 分别从 item_0 / item_1 中取出即可。

    上述操作只是为了讲解如何获取更深层次的 JSON 项,实际操作中会这么写:

    int iArraySize = cJSON_GetArraySize(pArray);
    for (i = 0; i < iArraySize; i++)
    {
        printf("******** Stu[%d] info ********\n", i + 1);
    
        cJSON *item = cJSON_GetArrayItem(pArray, i);
    
        cJSON *pName = cJSON_GetObjectItem(item, "name");
        printf("name  [%s]\n", pName->valuestring);
    
        cJSON *pAge = cJSON_GetObjectItem(item, "age");
        printf("age   [%d]\n", pAge->valueint);
    
        cJSON *pSex = cJSON_GetObjectItem(item, "sex");
        printf("sex   [%s]\n", pSex->valuestring);
    }

    就跟剥洋葱似的,先将外层的 stu_info 剥出来,然后在剥出内层的 item,最后将 name、age、sex 从 item 中分离出来。

    对于更多层次的 JSON 处理,也是同样的操作,你只需要保证在解析你所需的 JSON 项前,其父节点已被解析。

    完整代码如下:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include "cJSON.h"
    
    #define STRING_LEN_MAX 2048
    
    void GetJSONFromFile(const char *FILENAME, char **ppcJson)
    {
        FILE *fp = fopen(FILENAME, "r");
        if (NULL == fp)
        {
            printf("file open error\n");
            exit(0);
        }
    
        char *pcJson = (char *)malloc(STRING_LEN_MAX);
        memset(pcJson, 0, STRING_LEN_MAX);
    
        do
        {
            fgets(pcJson + strlen(pcJson), STRING_LEN_MAX - strlen(pcJson), fp);
        } while (!feof(fp));
    
        *ppcJson = pcJson;
    
        fclose(fp);
    }
    
    int main()
    {
        char *pcJson;
    
        GetJSONFromFile("test.json", &pcJson);
    
        cJSON *root = cJSON_Parse(pcJson);
        if (NULL == root)
        {
            printf("fail to call cJSON_Parse\n");
            exit(0);
        }
    
        cJSON *pClassName = cJSON_GetObjectItem(root, "class_name");
        printf("class name [%s]\n", pClassName->valuestring);
    
        cJSON *pStuNum = cJSON_GetObjectItem(root, "stu_num");
        printf("stu num    [%d]\n", pStuNum->valueint);
    
        cJSON *pArray = cJSON_GetObjectItem(root, "stu_info");
        if (NULL == pArray)
        {
            printf("not find stu_info\n");
            goto err;
        }
        int i;
        int iArraySize = cJSON_GetArraySize(pArray);
        for (i = 0; i < iArraySize; i++)
        {
            printf("******** Stu[%d] info ********\n", i + 1);
    
            cJSON *item = cJSON_GetArrayItem(pArray, i);
    
            cJSON *pName = cJSON_GetObjectItem(item, "name");
            printf("name  [%s]\n", pName->valuestring);
    
            cJSON *pAge = cJSON_GetObjectItem(item, "age");
            printf("age   [%d]\n", pAge->valueint);
    
            cJSON *pSex = cJSON_GetObjectItem(item, "sex");
            printf("sex   [%s]\n", pSex->valuestring);
        }
        
    err:
        cJSON_Delete(root); // 手动调用 cJSON_Delete 进行内存回收
    
        return 0;
    }

    3.4 序列化 cJSON 结构体

    前面我们一直在介绍如何将一个 JSON 字符串反序列化为 cJSON 结构体,下面我们来介绍一下如何将 cJSON 结构体序列化为 JSON 字符串。

    首先,我们要先有一个 cJSON 结构体,构造 cJSON 结构体的相关函数如下:

    函数 解释说明 返回值
    cJSON_CreateObject 创建一个 object 类型的 JSON 项 cJSON *
    cJSON_CreateArray 创建一个 array 类型的 JSON 项 cJSON *
    cJSON_CreateString 创建一个值为 string 类型的 JSON 项 cJSON *
    cJSON_CreateNumber 创建一个值为 number 类型的 JSON 项 cJSON *
    cJSON_AddItemToObject 将 JSON 项添加到 object 中 void
    cJSON_AddItemToArray 将 JSON 项添加到 array 中 void
    cJSON_AddNumberToObject 创建一个值为 number 类型的 JSON 项并添加到 JSON 对象中 void
    cJSON_AddStringToObject 创建一个值为 string 类型的 JSON 项并添加到 JSON 对象中 void
    cJSON_Print 将 cJSON 结构体序列化为 JSON 字符串(有格式) char *
    cJSON_PrintUnformatted 将 cJSON 结构体序列化为 JSON 字符串(无格式) char *
    cJSON_Delete 删除 cJSON 结构体 void

    3.4.1 一个简单的例子

    假设我们想要获取的 JSON 字符串为:

    {
        "name": "张三",
        "age": 18,
        "sex": "男"
    }

    我们该如何构造 cJSON 结构体呢?

    还记得这个 JSON 字符串反序列化的样子吗?不记得也没关系,因为我马上就要张贴了😝

    image-20221119225512840

    根据图示可知,我们要先有一个根节点 root。

    由于本次样例中的 JSON 字符串是一个 JSON 对象,所以我们需要通过cJSON_CreateObject()函数来创建一个 object 类型的 root:

    cJSON *root = cJSON_CreateObject();

    接下来我们需要将 name、age、sex 分别加入到 root 中:

    • 通过cJSON_AddStringToObject()将字符串类型的 name、sex 加入到 root 中
    • 通过cJSON_AddNumberToObject()将数值类型的 age 加入到 root 中

    具体操作如下:

    cJSON_AddStringToObject(root, "name", "张三");
    cJSON_AddNumberToObject(root, "age", 18);
    cJSON_AddStringToObject(root, "sex", "男");
    • cJSON_AddStringToObject(object,name,s):将键值对(name / s)加入到 object 中
    • cJSON_AddNumberToObject(object,name,n):将键值对(name / n)加入到 object 中

    经过上述操作,我们就可以得到如图 6 所示的 cJSON 结构体。那如何获取基于该结构体的 JSON 字符串呢?

    很简单,调用函数cJSON_Print()cJSON_PrintUnformatted()即可实现:

    char *pJsonFormatted = cJSON_Print(root);
    puts(pJsonFormatted);
    
    char *pJsonUnformatted = cJSON_PrintUnformatted(root);
    puts(pJsonUnformatted);

    cJSON_Print()cJSON_PrintUnformatted(),这两个 API 的区别在于:一个是没有格式的,也就是转换出的字符串中间不会有换行、对齐之类的格式存在。而 cJSON_Print 打印出来是我们看起来很舒服的格式,仅此而已。

    完整代码如下:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include "cJSON.h"
    
    int main()
    {
    	cJSON *root = cJSON_CreateObject();
    	cJSON_AddStringToObject(root, "name", "张三");
    	cJSON_AddNumberToObject(root, "age", 18);
    	cJSON_AddStringToObject(root, "sex", "男");
    
    	char *pJsonFormatted = cJSON_Print(root);
    	puts(pJsonFormatted);
    	
    	char *pJsonUnformatted = cJSON_PrintUnformatted(root);
    	puts(pJsonUnformatted);
    
    	cJSON_Delete(root); // 手动调用 cJSON_Delete 进行内存回收
    	
        // 记得回收 pJsonFormatted 和 pJsonUnformatted
    	if (NULL != pJsonFormatted)
    	{
    		free(pJsonFormatted);
    	}
    	if (NULL != pJsonUnformatted)
    	{
    		free(pJsonUnformatted);
    	}
    
    	return 0;
    }

    3.4.2 一个有一丢丢复杂的例子

    这次我们要获取的 JSON 字符串为:

    {
        "class_name": "计科一班",
        "stu_num"   : 2,
        "stu_info"  : 
        [
            {
                "name": "张三",
                "age": 18,
                "sex": "男"
            },
            {
                "name": "李四",
                "age": 20,
                "sex": "男"
            }
        ]
    }

    对应的反序列化后的模样如下图所示:

    image-20221119232056866

    具体做法为:

    1. 首先创建一个 root
    2. 将第二层的 class_name、stu_num、stu_info 加入到 root 中
    3. 构造两个 JSON 项 item_0 和 item_1,并将 name、age、sex 分别加入其中
    4. 最后将 JSON 项 加入到 stu_info 中

    大功告成,具体代码如下:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include "cJSON.h"
    
    int main()
    {
        // 步骤一
    	cJSON *root = cJSON_CreateObject();
    
        // 步骤二
    	cJSON_AddStringToObject(root, "class_name", "计科一班");
    	cJSON_AddNumberToObject(root, "stu_num", 2);
    	
    	cJSON *pArray = cJSON_CreateArray();
    	cJSON_AddItemToObject(root, "stu_info", pArray);
    
       	// 步骤三
    	cJSON *pObject_1 = cJSON_CreateObject();
    	cJSON_AddStringToObject(pObject_1, "name", "张三");
    	cJSON_AddNumberToObject(pObject_1, "age", 18);
    	cJSON_AddStringToObject(pObject_1, "sex", "男");
    	
    	cJSON *pObject_2 = cJSON_CreateObject();
    	cJSON_AddStringToObject(pObject_2, "name", "李四");
    	cJSON_AddNumberToObject(pObject_2, "age", 19);
    	cJSON_AddStringToObject(pObject_2, "sex", "男");
        
        // 步骤四
        cJSON_AddItemToArray(pArray, pObject_1);
    	cJSON_AddItemToArray(pArray, pObject_2);
    
    	char *pJson = cJSON_Print(root);
    	puts(pJson);
    
    
    	cJSON_Delete(root); // 手动调用 cJSON_Delete 进行内存回收
    	if (NULL != pJson)  // 回收 pJson
    	{
    		free(pJson);
    	}
    	return 0;
    }

    参考资料


    __EOF__

  • 本文作者: MElephant
  • 本文链接: https://www.cnblogs.com/hyacinthLJP/p/16908345.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    aix360-gec
    【c ++ primer 笔记】第3章 字符串、向量和数组
    代码随想录算法训练营第21天—回溯算法01 | ● 理论基础 ● *77. 组合
    pythom导出mysql指定binlog文件
    java+springboot基于微信小程序的在线办公系统 uniapp 小程序
    java8 线程
    30岁之前什么新技术我都学,30岁之后什么新技术我都不学。
    【补充】Java程序设计接口实验作业
    SpringBoot集成Mybatis-Plus
    mysql连接查询
  • 原文地址:https://www.cnblogs.com/hyacinthLJP/p/16908345.html