一款輕量級的JSON解析庫,用cJSON讓你看清王者榮耀(2021第一篇部落格)

DeRoy發表於2021-01-03



來源:微信公眾號「程式設計學習基地」

2021年的第一篇文章,帶你看清王者榮耀

JSON是一種輕量級的資料格式,應用廣泛。在C/C++應用中也常常作為配置檔案或者資料的儲存

JSON語法規則

JSON物件是一個無序的"名稱/值"鍵值對的集合:

  • 以"{“開始,以”}"結束,允許巢狀使用;
  • 每個名稱和值成對出現,名稱和值之間使用":"分隔;
  • 鍵值對之間用","分隔
  • 在這些字元前後允許存在無意義的空白符;

對於鍵值,可以有如下值:

  • 一個新的json物件
  • 陣列:使用"[“和”]"表示
  • 數字:直接表示,可以是整數,也可以是浮點數
  • 字串:使用引號"表示
  • 字面值:false、null、true中的一個(必須是小寫)

示例如下:

[{
	"ename": 105,
	"cname": "廉頗",
	"title": "正義爆轟",
	"new_type": 0,
	"hero_type": 3,
	"skin_name": "正義爆轟|地獄巖魂"
}, {
	"ename": 106,
	"cname": "小喬",
	"title": "戀之微風",
	"new_type": 0,
	"hero_type": 2,
	"skin_name": "戀之微風|萬聖前夜|天鵝之夢|純白花嫁|繽紛獨角獸"
}]

cJSON

cJSON下載使用

cJSON是使用ANSI C編寫的超輕量級的JSON解析器,因此在C中也常常是不二之選。

cJSON專案託管在Github上,倉庫地址如下:

https://github.com/DaveGamble/cJSON

使用Git命令將其拉取到本地:

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

從Github拉取cJSON原始碼後,檔案非常多,但是其中cJSON的原始碼檔案只有兩個:

  • cJSON.h
  • cJSON.c

使用的時候,只需要將這兩個檔案複製到工程目錄,然後包含標頭檔案cJSON.h即可,如下:

#include "cJSON.h"

詳細使用視訊:VS使用cJSON庫

關鍵資料結構

cJSON的關鍵資料結構如下:

typedef struct cJSON {  //cJSON結構體
       struct cJSON*next,*prev;           /*後驅節點和前驅節點*/
       struct cJSON *child;                   /*孩子節點*/
       int type;                                     /* 鍵的型別*/
       char *valuestring;                       /*字串值*/
       int valueint;                                /* 整數值*/
       double valuedouble;                    /* 浮點數值*/
       char *string;                               /* 鍵的名字*/
} cJSON;

json是一種組織良好的資料格式,因而JSON中的內容解析後,都可以通過以上資料結構進行處理。

例如,對於下面的json內容:

{
    "name":"程式設計學習基地",
    "site":"https://www.deroy.cn",
    "age":1
}

解析後,site將會是name的next節點,並且它的鍵型別是字串。

cJSON資料解析

常用介面函式

用於將字串解析成json物件,若失敗則返回NULL。

cJSON *cJSON_Parse(const char *value);

用於獲取json物件中的某個節點,若失敗,返回NULL,成功則返回該節點物件。

cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);

用於釋放json物件相關記憶體。

void   cJSON_Delete(cJSON *c);

如果JSON資料的值是陣列,可以通過下面介面獲取 JSON 陣列大小和陣列裡面的 JSON 物件

int cJSON_GetArraySize(const cJSON *array);
cJSON * cJSON_GetArrayItem(const cJSON *array, int index);

解析步驟

  • 將JSON檔案內容讀取到buffer
  • 通過cJSON介面解析buffer中的字串
  • 獲取JSON指定欄位

為了將JSON檔案的內容讀取到buffer,需要知道檔案的大小:

size_t get_file_size(const char *filepath)
{
    /*check input para*/
    if(NULL == filepath)
        return 0;
    struct stat filestat;
    memset(&filestat,0,sizeof(struct stat));
    /*get file information*/
    if(0 == stat(filepath,&filestat))
        return filestat.st_size;
    else
        return 0;
}

然後申請一段記憶體,將檔案中的文字讀取到buffer中:

char *read_file_to_buf(const char *filepath)
{
    /*check input para*/
    if(NULL == filepath)
    {
        return NULL;
    }
    /*get file size*/
    size_t size = get_file_size(filepath);
    if(0 == size)
        return NULL;

    /*malloc memory*/
    char *buf = malloc(size+1);
    if(NULL == buf)
        return NULL;
    memset(buf,0,size+1);

    /*read string from file*/
    FILE *fp = fopen(filepath,"r");
    size_t readSize = fread(buf,1,size,fp);
    if(readSize != size)
    {
        /*read error*/
        free(buf);
        buf = NULL;
    }

    buf[size] = 0;
    return buf;
}

再根據前面提到的解析流程,我們的JSON預解析函式如下:

cJSON *prepare_parse_json(const char *filePath)
{
    /*check input para*/
    if(NULL == filePath)
    {
        printf("input para is NULL\n");
        return NULL;
    }
    /*read file content to buffer*/
    char *buf = read_file_to_buf(filePath);
    if(NULL == buf)
    {
        printf("read file to buf failed\n");
        return NULL;
    }
    /*parse JSON*/
    cJSON *pTemp = cJSON_Parse(buf);
    free(buf);
    buf = NULL;
    return pTemp;
}

解析示例

#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<string.h>
#include"cJSON.h"

size_t get_file_size(const char *filepath)
{
    /*check input para*/
    if(NULL == filepath)
        return 0;
    struct stat filestat;
    memset(&filestat,0,sizeof(struct stat));
    /*get file information*/
    if(0 == stat(filepath,&filestat))
        return filestat.st_size;
    else
        return 0;
}

char *read_file_to_buf(const char *filepath)
{
    /*check input para*/
    if(NULL == filepath)
    {
        return NULL;
    }
    /*get file size*/
    size_t size = get_file_size(filepath);
    if(0 == size)
        return NULL;
        
    /*malloc memory*/
    char *buf = malloc(size+1);
    if(NULL == buf)
        return NULL;
    memset(buf,0,size+1);
    
    /*read string from file*/
    FILE *fp = fopen(filepath,"r");
    size_t readSize = fread(buf,1,size,fp);
    if(readSize != size)
    {
        /*read error*/
        free(buf);
        buf = NULL;
    }

    buf[size] = 0;
    return buf;
}
/**/
cJSON *prepare_parse_json(const char *filePath)
{
    /*check input para*/
    if(NULL == filePath)
    {
        printf("input para is NULL\n");
        return NULL;
    }
    /*read file content to buffer*/
    char *buf = read_file_to_buf(filePath);
    if(NULL == buf)
    {
        printf("read file to buf failed\n");
        return NULL;
    }
    /*parse JSON*/
    cJSON *pTemp = cJSON_Parse(buf);
    free(buf);
    buf = NULL;
    return pTemp;
}
int main(void)
{
    char *filename = "herolist.json";
    cJSON *pJson = NULL;
	cJSON *pTemp = NULL;
    cJSON *pVal = NULL;
    /*建立cJSON物件*/
    pJson = prepare_parse_json(filename);
    if(NULL == pJson)
    {
        printf("parse json failed\n");
        return -1;
    }
    /*獲取cJSON陣列數量*/
	int num = cJSON_GetArraySize(pJson);
    /*遍歷每一個cJSON陣列元素*/
	for(int index=0;index<num;index++)
	{
        /*獲取cJSON陣列中的第index個cJSON物件*/
		pTemp = cJSON_GetArrayItem(pJson,index);
        
		/*獲取cJSON物件中的key值為ename的物件*/
		pVal = cJSON_GetObjectItem(pTemp,"ename");
		printf("ename:%d\n",pVal->valueint);
		
		pVal = cJSON_GetObjectItem(pTemp,"cname");
		printf("cname:%s\n",pVal->valuestring);
		
		pVal = cJSON_GetObjectItem(pTemp,"title");
		printf("title:%s\n",pVal->valuestring);
		
		pVal = cJSON_GetObjectItem(pTemp,"new_type");
		printf("new_type:%d\n",pVal->valueint);
		
		pVal = cJSON_GetObjectItem(pTemp,"hero_type");
		printf("hero_type:%d\n",pVal->valueint);
		
		pVal = cJSON_GetObjectItem(pTemp,"skin_name");
		printf("skin_name:%s\n\n",pVal->valuestring);
		printf("====================================\n\n");
	}
    
    /*釋放記憶體*/
    cJSON_Delete(pJson);
    pJson = NULL;
    return 0;
}
gcc -o mian main.c cJSON.c

windows下VC6.0,VS也可以執行,但是因為編碼格式問題,我另寫了一套程式

為了讓輸出看起來舒服,改了點格式,輸出如下(刪減),原始碼獲取傳送關鍵字【王者榮耀】

|ename	|cname		|title		|new_type	|hero_type	|skin_name|
-----------------------------------------------------------------------------------------------------------
|105	|廉頗		|正義爆轟	|0		|3		|正義爆轟|地獄巖魂|
-----------------------------------------------------------------------------------------------------------
|106	|小喬		|戀之微風	|0		|2		|戀之微風|萬聖前夜|天鵝之夢|純白花嫁|繽紛獨角獸|
-----------------------------------------------------------------------------------------------------------
|107	|趙雲		|蒼天翔龍	|0		|1		|蒼天翔龍|忍●炎影|未來紀元|皇家上將|嘻哈天王|白執事|引擎之心|
-----------------------------------------------------------------------------------------------------------
|108	|墨子		|和平守望	|0		|2		|和平守望|金屬風暴|龍騎士|進擊墨子號|
-----------------------------------------------------------------------------------------------------------

JSON資料封裝

封裝方法

封裝JSON資料的過程,其實就是建立連結串列和向連結串列中新增節點的過程。

首先來講述一下連結串列中的一些術語:

  • 頭指標:指向連結串列頭結點的指標;
  • 頭結點:不存放有效資料,方便連結串列操作;
  • 首節點:第一個存放有效資料的節點;
  • 尾節點:最後一個存放有效資料的節點;

封裝步驟

明白了這幾個概念之後,我們開始講述建立一段完整的JSON資料,即如何建立一條完整的連結串列。

  • ① 建立頭指標:
 cJSON* cjson_test = NULL;
  • ② 建立頭結點,並將頭指標指向頭結點:
cjson_test = cJSON_CreateObject();
  • ③ 盡情的向連結串列中新增節點:
/* 新增一個值為 null 的布林型別的JSON資料(新增一個連結串列節點) */
cJSON_AddNullToObject(cJSON * const object, const char * const name);
/* 新增一個值為 true 的布林型別的JSON資料(新增一個連結串列節點) */
cJSON_AddTrueToObject(cJSON * const object, const char * const name);
/* 新增一個值為 False 的布林型別的JSON資料(新增一個連結串列節點) */
cJSON_AddFalseToObject(cJSON * const object, const char * const name);
/* 新增一個值為布林型別的JSON資料 0:false 非0:true (新增一個連結串列節點) */
cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
/* 新增一條數值型別的JSON資料(新增一個連結串列節點) */
cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
/* 新增一條字串型別的JSON資料(新增一個連結串列節點) */
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);
/* 新增一個巢狀的JSON物件/陣列(新增一個連結串列節點) */
cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);

cJSON還提供了將JSON物件轉換成字串輸出到終端

char *cJSON_Print(const cJSON *item);

封裝示例

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

    /* 新增一個值為 null 的布林型別的JSON資料(新增一個連結串列節點) */
    cJSON_AddNullToObject(cjson_test, "null_test");

    /* 新增一個值為 true 的布林型別的JSON資料(新增一個連結串列節點) */
    cJSON_AddTrueToObject(cjson_test,"true_test");
    /* 新增一個值為 False 的布林型別的JSON資料(新增一個連結串列節點) */
    cJSON_AddFalseToObject(cjson_test, "false_test");
    /* 新增一個值為布林型別的JSON資料 0:false 非0:true (新增一個連結串列節點) */
    cJSON_AddBoolToObject(cjson_test, "bool_test", 0);

    /* 新增一條整數型別的JSON資料(新增一個連結串列節點) */
    cJSON_AddNumberToObject(cjson_test, "int_test", 22);

    /* 新增一條浮點型別的JSON資料(新增一個連結串列節點) */
    cJSON_AddNumberToObject(cjson_test, "double_test", 55.5);

    /* 新增一條字串型別的JSON資料(新增一個連結串列節點) */
    cJSON_AddStringToObject(cjson_test, "str_test", "我是字串");

    /* 新增一行任意資料(新增一個連結串列節點) */
    cJSON_AddRawToObject(cjson_test, "key", "任意資料");

    /* 新增一個空物件(新增一個連結串列節點) */
    cJSON_AddObjectToObject(cjson_test, "objet");

    /* 新增一個巢狀的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);

    /* 新增一個空陣列(新增一個連結串列節點) */
    cJSON_AddArrayToObject(cjson_test, "Array");

    /* 新增一個陣列型別的JSON資料(新增一個連結串列節點) */
    cjson_skill = cJSON_CreateArray();
    cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "C" ));
    cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "C++" ));
    cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Python" ));
    cJSON_AddItemToArray(cjson_skill, cJSON_CreateString( "Java"));
    cJSON_AddItemToObject(cjson_test, "skill", cjson_skill);

    /* 列印JSON物件(整條連結串列)的所有資料 */
    str = cJSON_Print(cjson_test);
    printf("%s\n", str);

    return 0;
}

輸出結果:

{
	"null_test":	null,
	"true_test":	true,
	"false_test":	false,
	"bool_test":	false,
	"int_test":	22,
	"double_test":	55.5,
	"str_test":	"我是字串",
	"key":	任意資料,
	"objet":	{
	},
	"address":	{
		"country":	"China",
		"zip-code":	111111
	},
	"Array":	[],
	"skill":	["C", "C++", "Python", "Java"]
}

完整程式碼:

連結:https://pan.baidu.com/s/1Pej2aKKPW5d0_cmUMgm5HQ
提取碼:18re

相關文章