Skip to content

LV010-cJSON简介

本文主要是C语言基础——cJSON简介与使用的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

一、cJSON简介

1. cJSON是什么?

cJSON是一个使用C语言编写的JSON数据解析器,具有超轻便,可移植,单文件的特点,使用MIT开源协议。项目托管在Github上,仓库地址在这里:GitHub - DaveGamble/cJSON: Ultralightweight JSON parser in ANSI C

2. 源码说明

我们可以用以下命令拉取cJSON源码:

shell
git clone https://github.com/DaveGamble/cJSON.git

从Github拉取cJSON源码后,文件非常多,但是其中cJSON的源码文件只有两个:cJSON.hcJSON.c。使用的时候,只需要将这两个文件复制到工程目录,然后包含头文件cJSON.h即可,如下:

c
#include "cJSON.h"

Tips:我拉取的时候,最新一次提交是这个:DaveGamble/cJSON at 12c4bf1986c288950a3d06da757109a6aa1ece38

image-20260311122554030

3. 源码编译

下载完源码后,仓库是有提供demo的,我们可以直接编译的,说明文档在这里:GitHub - DaveGamble/cJSON: Ultralightweight JSON parser in ANSI C

shell
# 生成Makefile文件
mkdir build
cd build
cmake ..

# 编译
make

但是其实可以直接在源码目录下执行make命令进行编译,编译完毕会得到一个名为 cJSON_test 的可执行文件,我们直接./就可以运行。

二、cJSON源码说明

1. cJSON数据结构

cJSON使用cJSON结构体来表示一个JSON数据,定义在 cJSON.h中,源码如下:

c
/* 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;

首先,它不是将一整段JSON数据抽象出来,而是将其中的一条JSON数据抽象出来,也就是一个键值对,用上面的结构体 strcut cJSON 来表示,其中用来存放值的成员列表如下:

c
int type;           // 用于表示该键值对中值的类型
char *valuestring;  // 如果键值类型(type)是字符串,则将该指针指向键值
int valueint;       // 如果键值类型(type)是整数,则将该指针指向键值
double valuedouble; // 如果键值类型(type)是浮点数,则将该指针指向键值
char *string;       // 用于表示该键值对的名称

其次,一段完整的JSON数据中由很多键值对组成,并且涉及到键值对的查找、删除、添加,所以使用链表来存储整段JSON数据,如上面的代码所示:

c
struct cJSON *next; // 指向下一个键值对
struct cJSON *prev; // 指向上一个键值对

最后,因为JSON数据支持嵌套,所以一个键值对的值会是一个新的JSON数据对象(一条新的链表),也有可能是一个数组,方便起见,在cJSON中,数组也表示为一个数组对象,用链表存储,所以:

c
struct cJSON *child; // 在键值对结构体中,当该键值对的值是一个嵌套的JSON数据或者一个数组时,由child指针指向该条新链表。

2. JSON数据封装

封装JSON数据的过程,其实就是创建链表和向链表中添加节点的过程。

c
cJSON *root = NULL;
c
root = cJSON_CreateObject();
c
/* Helper functions for creating and adding items to an object at the same time.
 * They return the added item or NULL on failure. */
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);

3. 输出JSON数据

一段完整的JSON数据就是一条长长的链表,那么,如何打印出这段JSON数据呢?cJSON提供了一个API,可以将整条链表中存放的JSON信息输出到一个字符串中:cJSON/cJSON.c at master · DaveGamble/cJSON · GitHub

c
/* Render a cJSON item/entity/structure to text. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item)
{
    return (char*)print(item, true, &global_hooks);
}

print函数定义在这里:cJSON/cJSON.c at master · DaveGamble/cJSON · GitHub。这里就不详细去了解了,就是一个遍历链表的过程。

使用cJSON_Print()函数打印JSON数据的时候,只需要接收该函数返回的指针地址即可。

4. JSON数据解析

解析JSON数据的过程,其实就是剥离一个一个链表节点(键值对)的过程。

  • (1)创建链表头指针
c
cJSON* root = NULL;
  • (2)解析整段JSON数据,并将链表头结点地址返回,赋值给头指针
c
//CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); // 解析整段数据使用的API只有这一个
root = cJSON_Parse(JSON_data);
  • (3)根据键值对的名称从链表中取出对应的值,返回该键值对(链表节点)的地址
c
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string)
  • (4)如果JSON数据的值是数组,使用下面的两个API提取数据
c
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);

三、使用实例

1. JSON数据封装实例

1.1 JSON数据

这里设计一个JSON的结构:

json
{
    "name": "sumu",
    "age": 28,
    "weight": 69.8,
    "address":
    {
        "country": "China",
        "city": "shanghai",
        "zip-code": 999999
    },
    "skill": ["c", "html", "Python"],
    "student": false
}

1.2 代码编写

这里只写主函数吧,剩下的就是cJSON.ccJSON.h

c
#include <stdio.h>
#include "cJSON.h"

int main(int argc, const char * argv[])
{
    cJSON* root = NULL;
    cJSON* cjson_address = NULL;
    cJSON* cjson_skill = NULL;
    char* str = NULL;

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

    /* 添加一条字符串类型的JSON数据(添加一个链表节点) */
    cJSON_AddStringToObject(root, "name", "sumu");

    /* 添加一条整数类型的JSON数据(添加一个链表节点) */
    cJSON_AddNumberToObject(root, "age", 28);

    /* 添加一条浮点类型的JSON数据(添加一个链表节点) */
    cJSON_AddNumberToObject(root, "weight", 69.8);

    /* 添加一个嵌套的JSON数据(添加一个链表节点) */
    cjson_address = cJSON_CreateObject();
    cJSON_AddStringToObject(cjson_address, "country", "China");
    cJSON_AddStringToObject(cjson_address, "city", "shanghai");
    cJSON_AddNumberToObject(cjson_address, "zip-code", 999999);
    cJSON_AddItemToObject(root, "address", cjson_address);

    /* 添加一个数组类型的JSON数据(添加一个链表节点) */
    cjson_skill = cJSON_CreateArray();
    cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "C" ));
    cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "html" ));
    cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Python" ));
    cJSON_AddItemToObject(root, "skill", cjson_skill);

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

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

    return 0;
}

1.3 编译源码

shell
gcc cJSON.c main.c -o cJSON_test

1.4 运行测试

我们直接执行即可:

shell
sumu@sumu-virtual-machine:~/7Linux/cJSON-master$ ./cJSON_test
{
        "name": "sumu",
        "age":  28,
        "weight":       69.8,
        "address":      {
                "country":      "China",
                "city": "shanghai",
                "zip-code":     999999
        },
        "skill":        ["C", "html", "Python"],
        "student":      false
}

1.5 数据链表关系图

我根据自己的理解画了个简图如下,来帮助理解:

image-20241222170030853

2. JSON数据解析实例

2.1 JSON数据

c
char *JSON_data = 
"{                              \
    \"name\":\"sumu\",   \
    \"age\": 28,                \
    \"weight\": 69.8,           \
    \"address\":                \
        {                       \
            \"country\": \"China\",\
            \"city\": \"shagnhai\",\
            \"zip-code\": 999999\
        },                      \
    \"skill\": [\"c\", \"html\", \"Python\"],\
    \"student\": false          \
}";

2.2 代码编写

c
#include <stdio.h>

#include "../cJSON/cJSON.h"

char *JSON_data = 
"{                              \
    \"name\":\"sumu\",   \
    \"age\": 28,                \
    \"weight\": 69.8,           \
    \"address\":                \
        {                       \
            \"country\": \"China\",\
            \"city\": \"shagnhai\",\
            \"zip-code\": 999999\
        },                      \
    \"skill\": [\"c\", \"html\", \"Python\"],\
    \"student\": false          \
}";

int main(void)
{
    cJSON* root = 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_city = NULL;
    cJSON* cjson_address_zipcode = NULL;

    cJSON* cjson_skill = NULL;
    cJSON* cjson_skill_item = NULL;
    int    skill_array_size = 0;
    int    i = 0;

    cJSON* cjson_student = NULL;

    /* 解析整段JSO数据 */
    root = cJSON_Parse(JSON_data);
    if(root == NULL)
    {
        printf("parse fail.\n");
        return -1;
    }

    /* 依次根据名称提取JSON数据(键值对) */
    cjson_name = cJSON_GetObjectItem(root, "name");
    cjson_age = cJSON_GetObjectItem(root, "age");
    cjson_weight = cJSON_GetObjectItem(root, "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(root, "address");
    cjson_address_country = cJSON_GetObjectItem(cjson_address, "country");
    cjson_address_city = cJSON_GetObjectItem(cjson_address, "city");
    cjson_address_zipcode = cJSON_GetObjectItem(cjson_address, "zip-code");
    printf("address-country:%s\naddress-city:%s\naddress-zipcode:%d\n", 
        cjson_address_country->valuestring, cjson_address_city->valuestring, cjson_address_zipcode->valueint);

    /* 解析数组 */
    cjson_skill = cJSON_GetObjectItem(root, "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(root, "student");
    if(cjson_student->valueint == 0)
    {
        printf("student: false\n");
    }
    else
    {
        printf("student:error\n");
    }
    
    return 0;
}

2.3 编译源码

shell
gcc cJSON.c main.c -o cJSON_test

2.4 运行测试

shell
sumu@sumu-virtual-machine:~/7Linux/cJSON-master$ ./cJSON_test 
name: sumu
age:28
weight:69.8
address-country:China
address-city:shagnhai
address-zipcode:999999
skill:[c,html,Python]
student: false

参考资料

cJSON使用详细教程 | 一个轻量级C语言JSON解析器-CSDN博客

C/C++ 使用cjson库 操作Json格式文件(创建、插入、解析、修改、删除)_C++ json-CSDN博客