首页 > 其他分享 >cJson 学习笔记

cJson 学习笔记

时间:2022-11-20 13:57:44浏览次数:78  
标签:name cJson age 笔记 sex 学习 cJSON JSON root

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/GgcJZvHBdPq

编译环境:CentOS 7

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

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

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

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

由于源码中使用了 pow 函数,所以一定要链接上 math 库,也就是 -l m 指令。

如果在编译过程中报好多 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <ctype.h>
#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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <ctype.h>
#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 字符串反序列化的样子吗?不记得也没关系,因为我马上就要张贴了

标签:name,cJson,age,笔记,sex,学习,cJSON,JSON,root
From: https://www.cnblogs.com/hyacinthLJP/p/16908345.html

相关文章

  • 狂神说Javase学习2
    JAVA方法详解方法的定义System.out.pringln(),那么它是什么呢类.对象.方法()设计方法的原则:就是一个方法只完成一个功能java的方法类似于其它语言的函数,是一段用来完成......
  • 2022-2023-1 20221313《计算机基础与程序设计》第十二周学习总结
    2022-2023-120221313《计算机基础与程序设计》第十二周学习总结作业信息作业课程https://edu.cnblogs.com/campus/besti/2022-2023-1-CFAP作业要求https://ww......
  • 西瓜书笔记之一
    ......
  • 【JAVA笔记】JAVA之IDEA快捷键指令汇总01
    一、IDEA常用快捷按键(1)代替鼠标操作快捷键智能提示:Alt+回车(常用)自动代码自动补全函数括号、分号、当前行缩进:Ctrl+Shift+回车提示代码模板:Ctrl+J使用xx块环......
  • Semantic Relation Reasoning for Shot-Stable Few-Shot Object Detection阅读笔记
    原文链接:https://arxiv.org/pdf/2103.01903.pdf摘要:由于真实世界数据服从长尾分布,导致小样本目标检测的性能在很大程度上受数据缺乏的新类影响。但是新类和基类之间的语义......
  • Seata 1.5.2 源码学习(Client端)
    在上一篇中通过阅读Seata服务端的代码,我们了解到TC是如何处理来自客户端的请求的,今天这一篇一起来了解一下客户端是如何处理TC发过来的请求的。要想搞清楚这一点,还得从Globa......
  • 深入解析CSS读书笔记·第1、2章
    层叠、优先级和继承层叠及优先级CSS本质是在声明规则,而层叠是为了解决规则之间冲突,优先级解决冲突的工具。层叠优先级的判断顺序是:来源->选择器->顺序。来源指样式的来......
  • Day3学习: Hello World
    HelloWorld!建立文件夹,用于存放代码新建一个java文件后缀为javaHello.java编写代码publicclassHello{publicstaticvoidmain(String[]args){......
  • 【学习笔记/习题总结】kruskal重构树
    kruskal重构树注:默认您学会了求最小生成树的kruskal算法,并且知道何为最小瓶颈生成树和最小瓶颈路。定义:在跑kruskal的过程中我们会从小到大加入若干条边,我们仍然按......
  • 学习ASP.NET Core Blazor编程系列十——路由(下)
    学习ASP.NETCoreBlazor编程系列一——综述学习ASP.NETCoreBlazor编程系列二——第一个Blazor应用程序(上)学习ASP.NETCoreBlazor编程系列二——第一个Blazor应......