cJSON 庫的使用
作者: 蘇丙榅
鏈接: cJson 庫的使用 | 愛編程的大丙
鏈接:Json | 愛編程的大丙
來源: 愛編程的大丙
供自己備忘;
一、Json
JSON(JavaScrip Object Notation) 是一種輕量級的數據交換格式。它基于 ECMAScript (歐洲計算機協會制定的 js 規范)的一個子集,采用完全獨立于編程語言的文本格式來存儲和表示數據。簡潔和清晰的層次結構使得 JSON 成為理想的數據交換語言。 易于人閱讀和編寫,同時也易于機器解析和生成,并有效地提升網絡傳輸效率。
關于上面的描述可以精簡為一句話:Json 是一種數據格式,和語言無關,在什么語言中都可以使用 Json。基于這種通用的數據格式,一般處理兩方面的任務:
- 組織數據(數據序列化),用于數據的網絡傳輸
- 組織數據(數據序列化),寫磁盤文件實現數據的持久化存儲(一般以 .json 作為文件后綴)
Json 中主要有兩種數據格式:Json 數組和 Json 對象,并且這兩種格式可以交叉嵌套使用,下面依次介紹下這兩種數據格式:
1. Json 數組
Json 數組使用 [] 表示,[] 里邊是元素,元素和元素之間使用逗號間隔,最后一個元素后邊沒有逗號,一個 Json 數組中支持同時存在多種不同類型的成員,包括:整形、 浮點、 字符串、 布爾類型、 json 數組、 json 對象、 空值-null。由此可見 Json數組比起 C/C++ 數組要靈活很多。
- Json 數組中的元素數據類型一致
// 整形
[1,2,3,4,5]
// 字符串
["luffy", "sanji", "zoro", "nami", "robin"]
- Json 數組中的元素數據類型不一致
[12, 13.34, true, false, "hello,world", null]
- Json 數組中的數組嵌套使用
[
["cat", "dog", "panda", "beer", "rabbit"],
["北京", "上海", "天津", "重慶"],
["luffy", "boy", 19]
]
- Json 數組和對象嵌套使用
[
{
"luffy":{
"age":19,
"father":"Monkey·D·Dragon",
"grandpa":"Monkey D Garp",
"brother1":"Portgas D Ace",
"brother2":"Sabo"
}
}
]
2. Json 對象
Json 對象使用 {} 來描述,每個 Json 對象中可以存儲若干個元素,每一個元素對應一個鍵值對(key:value 結構),元素和元素之間使用逗號間隔,最后一個元素后邊沒有逗號。對于每個元素中的鍵值對有以下細節需要注意:
- 鍵值(key)必須是字符串,位于同一層級的鍵值不要重復(因為是通過鍵值取出對應的value 值)
- value 值的類型是可選的,可根據實際需求指定,可用類型包括:整形、 浮點、 字符串、 布爾類型、 json 數組、 json 對象、 空值-null。
使用 Json 對象描述一個人的信息:
{
"Name":"Ace",
"Sex":"man",
"Age":20,
"Family":{
"Father":"Gol·D·Roger",
"Mother":"Portgas·D·Rouge",
"Brother":["Sabo", "Monkey D. Luffy"]
},
"IsAlive":false,
"Comment":"yyds"
}
3. 注意事項
通過上面的介紹可用看到,Json 的結構雖然簡單,但是進行嵌套之后就可以描述很復雜的事情,在項目開發過程中往往需要我們根據實際需求自己定義 Json 格式用來存儲項目數據。
另外,如果需要將 Json 數據持久化到磁盤文件中,需要注意一個問題:在一個 Json 文件中只能有一個 Json 數組或者 Json 對象的根節點,不允許同時存儲多個并列的根節點。下面舉例說明:
- 錯誤的寫法
// test.json
{
"name":"luffy",
"age":19
}
{
"user":"ace",
"passwd":"123456"
}
錯誤原因:在一個 Json 文件中有兩個并列的 Json 根節點(并列包含 Json 對象和 Json 對象、Json 對象和 Json 數組、Json 數組和 Json 數組),根節點只能有一個。
- 正確的寫法
// test.json
{
"Name":"Ace",
"Sex":"man",
"Age":20,
"Family":{
"Father":"Gol·D·Roger",
"Mother":"Portgas·D·Rouge",
"Brother":["Sabo", "Monkey D. Luffy"]
},
"IsAlive":false,
"Comment":"yyds"
}
在上面的例子中通過 Json 對象以及 Json 數組的嵌套描述了一個人的身份信息,并且根節點只有一個就是 Json 對象,如果還需要使用 Json 數組或者 Json 對象描述其他信息,需要將這些信息寫入到其他文件中,不要和這個 Json 對象并列寫入到同一個文件里邊,切記!!!
二、cJSON 庫的使用
cJSON 是一個超輕巧,攜帶方便,單文件,簡單的可以作為 ANSI-C 標準的 JSON 解析器。
cJSON 是一個開源項目,github下載地址:https://github.com/DaveGamble/cJSON
cJSON,目前來說,主要的文件有兩個,一個 cJSON.c 一個 cJSON.h。使用的時候,將頭文件 include 進去即可。如果是在 Linux 操作系統中使用,編譯到時候需要添加數學庫 libm.so,如下所示:
gcc *.c cJSON.c -lm
1. cJSON 結構體
在 cJSON.h 中定義了一個非常重要的結構體 cJSON,想要熟悉使用 cJSON 庫函數可從 cJSON 結構體入手,cJSON 結構體如下所示:
typedef struct cJSON {
struct cJSON *next,*prev;
struct cJSON *child;
int type;
char *valuestring; // value值是字符串類型
int valueint;
double valuedouble;
char *string; // 對象中的key
} cJSON;
關于這個結構體做如下幾點的說明:
-
cJOSN 結構體是一個雙向鏈表,并且可通過 child 指針訪問下一層。
-
結構體成員 type 變量用于描述數據元素的類型(如果是鍵值對表示 value 值的類型),數據元素可以是字符串可以是整形,也可以是浮點型。
-
如果是整形值的話可通過 valueint 將值取出
-
如果是浮點型的話可通過 valuedouble 將值取出
-
如果是字符串類型的話可通過 valuestring 將值取出
-
-
結構體成員 string 表示鍵值對中鍵值的名稱。
cJSON 作為 Json 格式的解析庫,其主要功能就是構建和解析 Json 格式了,比如要發送數據:用途就是發送端將要發送的數據以 Json 形式封裝,然后發送,接收端收到此數據后,還是按 Json 形式解析,就得到想要的數據了。
2. cJSON API
Json 格式的數據無外乎有兩種 Json 對象和 Json 數組,創建的 Json 數據串可能是二者中的一種,也可能是二者的組合,不管哪一種通過調用相關的 API 函數都可以輕松的做到這一點。
2.1 數據的封裝
在 cJSON.h 頭文件中可以看到一些函數聲明,通過調用這些創建函數就可以將 Json 支持的數據類型封裝為 cJSON 結構體類型:
// 空值類型
extern cJSON *cJSON_CreateNull(void);
// 布爾類型
extern cJSON *cJSON_CreateTrue(void);
extern cJSON *cJSON_CreateFalse(void);
extern cJSON *cJSON_CreateBool(int b);
// 數值類型
extern cJSON *cJSON_CreateNumber(double num);
// 字符串類型
extern cJSON *cJSON_CreateString(const char *string);
// json數組(創建空數組)
extern cJSON *cJSON_CreateArray(void);
// json對象(創建空對象)
extern cJSON *cJSON_CreateObject(void);
另外,cJSON 庫中還給我我們提供了一些更為簡便的操作函數,在創建數組的同時還可以進行初始化
// 創建一個Json數組, 元素為整形
extern cJSON *cJSON_CreateIntArray(const int *numbers, int count);
// 創建一個Json數組, 元素為浮點
extern cJSON *cJSON_CreateFloatArray(const float *numbers, int count);
extern cJSON *cJSON_CreateDoubleArray(const double *numbers, int count);
// 創建一個Json數組, 元素為字符串類型
extern cJSON *cJSON_CreateStringArray(const char **strings, int count);
2.2 Json 對象操作
當得到一個 Json 對象之后,就可以往對象中添加鍵值對了,可以使用 cJSON_AddItemToObject()
extern void cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
在 cJSON 庫中節點的從屬關系是通過樹來維護的,每一層節點都是通過鏈表來維護的,這樣就能分析出該函數參數的含義:
-
object:要添加的鍵值對從屬于那個節點
-
string:添加的鍵值對的鍵值
-
item:添加的鍵值對的 value 值(需要先將其封裝為 cJSON 類型的結構體)
為了讓我的操作更加方便,cJson 庫還給我們提供了一些宏函數,方便我們快速的往 Json 對象中添加鍵值對
#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull())
#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue())
#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse())
#define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b))
#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n))
#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s))
我們還可以根據 Json 對象中的鍵值取出相應的 value 值,API 函數原型如下:
extern cJSON *cJSON_GetObjectItem(cJSON *object, const char *string);
2.3 Json 數組操作
添加數據到 Json 數組中(原始數據需要先轉換為 cJSON 結構體類型)
extern void cJSON_AddItemToArray(cJSON *array, cJSON *item);
得到 Json 數組中元素的個數:
extern int cJSON_GetArraySize(cJSON *array);
得到 Json 數組中指定位置的原素,如果返回 NULL 表示取值失敗了。
extern cJSON *cJSON_GetArrayItem(cJSON *array, int item);
2.4 序列化
序列化就是將 Json 格式的數據轉換為字符串的過程,cJSON 庫中給我們提供了 3 個轉換函數,具體如下:
第一個參數 item 表示 Json 數據塊的根節點。
extern char *cJSON_Print(cJSON *item);
extern char *cJSON_PrintUnformatted(cJSON *item);
extern char *cJSON_PrintBuffered(cJSON *item, int prebuffer, int fmt);
-
調用 cJSON_Print() 函數我們可以得到一個帶格式的 Json 字符串(有換行,看起來更直觀)
-
調用 cJSON_PrintUnformatted() 函數會得到一個沒有格式的 Json 字符串(沒有換行,所有的數據都在同一行)。
-
調用 cJSON_PrintBuffered() 函數使用緩沖策略將 Json 實體轉換為字符串,參數 prebuffer 是指定緩沖區的大小,參數 fmt==0 表示未格式化,fmt==1 表示格式化。
我們在編碼過程中可以根據自己的實際需求調用相關的操作函數得到對應格式的 Json 字符串。
2.5 Json字符串的解析
如果我們得到了一個 Json 格式的字符串,想要讀出里邊的數據,就需要對這個字符串進行解析,處理方式就是將字符串轉換為 cJSON 結構體,然后再基于這個結構體讀里邊的原始數據,轉換函數的函數原型如下:
extern cJSON *cJSON_Parse(const char *value);
2.6 內存釋放
當我們將數據封裝為 cJSON 結構類型的節點之后都會得到一塊堆內存,當我們釋放某個節點的時候可以調用 cJson 庫提供的刪除函數 cJSON_Delete(),函數原型如下:
extern void cJSON_Delete(cJSON *c);
該函數的參數為要釋放的節點的地址,在此強調一點:在進行內存地址釋放的時候,當前節點以及其子節點都會被刪除。
3. Json 數據的封裝
3.1 Json 對象操作舉例
創建一個對象,并向這個對象里添加字符串和整型鍵值:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"cJSON.h"
int main()
{
cJSON *root;
root=cJSON_CreateObject(); // 創建根數據對象
cJSON_AddStringToObject(root, "name", "luffy"); // 添加鍵值對
cJSON_AddStringToObject(root, "sex", "man"); // 添加鍵值對
cJSON_AddNumberToObject(root, "age", 19); // 添加鍵值對
char *out = cJSON_Print(root); // 將json形式轉換成字符串
printf("%s\n",out);
// 釋放內存
cJSON_Delete(root);
free(out);
}
運行結果
{
"name": "luffy",
"sex": "man",
"age": 19
}
若干說明:
-
cJSON_CreateObject 函數可創建一個根對象,返回的是一個 cJSON 指針,在這個指針用完了以后,需要手動調用 cJSON_Delete(root) 進行內存回收。
-
函數 cJSON_Print() 內部封裝了 malloc 函數,所以需要使用 free() 函數釋放被 out 占用的內存空間。
3.2 Json 數組操作舉例
創建一個數組,并向數組添加一個字符串和一個數字
int main(int argc, char **argv)
{
cJSON *root;
root = cJSON_CreateArray();
cJSON_AddItemToArray(root, cJSON_CreateString("Hello world"));
cJSON_AddItemToArray(root, cJSON_CreateNumber(10));
// char *s = cJSON_Print(root);
char *s = cJSON_PrintUnformatted(root);
if (s)
{
printf(" %s \n", s);
free(s);
}
cJSON_Delete(root);
return 0;
}
運行結果:
["Hello world",10]
3.3 Json 對象、數組嵌套使用
對象里面包括一個數組,數組里面包括對象,對象里面再添加一個字符串和一個數字
{
"person":[{
"name":"luffy",
"age":19
}]
}
示例代碼:
int main(int argc, char **argv)
{
cJSON *root, *body, *list;
// josn 對象 root
root = cJSON_CreateObject();
// root 添加鍵值對 person:json數組A
cJSON_AddItemToObject(root,"person", body = cJSON_CreateArray());
// json數組A 添加Json對象B
cJSON_AddItemToArray(body, list = cJSON_CreateObject());
// 在json對象B中添加鍵值對: "name":"luffy"
cJSON_AddStringToObject(list, "name", "luffy");
// 在json對象B中添加鍵值對: "age":19
cJSON_AddNumberToObject(list, "age", 19);
// char *s = cJSON_Print(root);
char *s = cJSON_PrintUnformatted(root);
if (s)
{
printf(" %s \n",s);
free(s);
}
if (root)
{
cJSON_Delete(root);
}
return 0;
}
運行結果
{"person":[{"name":"luffy","age":19}]}
4. 解析 Json 字符串
4.1 解析 Json 對象
Json 字符串的解析流程和數據的封裝流程相反,假設我們有這樣一個 Json 字符串(字符串中的雙引號需要通過轉義字符將其轉譯為普通字符):
{\"name\":\"luffy\",\"sex\":\"man\",\"age\":19}
示例代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cJSON.h"
int main()
{
cJSON *json, *name, *sex, *age;
char *out="{\"name\":\"luffy\",\"sex\":\"man\",\"age\":19}";
json = cJSON_Parse(out); //解析成json形式
name = cJSON_GetObjectItem(json, "name"); //獲取鍵值內容
sex = cJSON_GetObjectItem(json, "sex");
age = cJSON_GetObjectItem(json, "age");
printf("name:%s,sex:%s,age:%d\n", name->valuestring, sex->valuestring, age->valueint);
cJSON_Delete(json); //釋放內存
}
輸出的結果:
name:luffy,sex:man,age:19
如果是在嚴格的場所,應該先判定每個 item 的 type,然后再考慮去取值。
4.2 解析嵌套的 Json 對象
加大一點難度,下面我們解析一個嵌套的 Json 對象,數據如下:
{\"list\":{\"name\":\"luffy\",\"age\":19},\"other\":{\"name\":\"ace\"}}
示例代碼如下:
int main()
{
char *s = "{\"list\":{\"name\":\"luffy\",\"age\":19},\"other\":{\"name\":\"ace\"}}";
cJSON *root = cJSON_Parse(s);
if (!root)
{
printf("get root faild !\n");
return -1;
}
cJSON *js_list = cJSON_GetObjectItem(root, "list");
if (!js_list)
{
printf("no list!\n");
return -1;
}
printf("list type is %d\n", js_list->type);
cJSON *name = cJSON_GetObjectItem(js_list, "name");
if (!name)
{
printf("No name !\n");
return -1;
}
printf("name type is %d\n", name->type);
printf("name is %s\n", name->valuestring);
cJSON *age = cJSON_GetObjectItem(js_list, "age");
if (!age)
{
printf("no age!\n");
return -1;
}
printf("age type is %d\n", age->type);
printf("age is %d\n", age->valueint);
cJSON *js_other = cJSON_GetObjectItem(root, "other");
if (!js_other)
{
printf("no list!\n");
return -1;
}
printf("list type is %d\n", js_other->type);
cJSON *js_name = cJSON_GetObjectItem(js_other, "name");
if (!js_name)
{
printf("No name !\n");
return -1;
}
printf("name type is %d\n", js_name->type);
printf("name is %s\n", js_name->valuestring);
if (root)
{
cJSON_Delete(root);
}
return 0;
}
1.7.18 版本,打印結果如下:
list type is 64
name type is 16
name is luffy
age type is 8
age is 19
list type is 64
name type is 16
name is ace
4.3 解析 Json 數組
如果我們遇到的 Json 字符串是一個 Json 數組格式,處理方式和 Json 對象差不多,比如我們要解析如下字符串:
{\"names\":[\"luffy\",\"robin\"]}
示例代碼如下:
int main(int argc, char **argv)
{
char *s = "{\"names\":[\"luffy\",\"robin\"]}";
cJSON *root = cJSON_Parse(s);
if (!root)
{
printf("get root faild !\n");
return -1;
}
cJSON *js_list = cJSON_GetObjectItem(root, "names");
if (!js_list)
{
printf("no list!\n");
return -1;
}
int array_size = cJSON_GetArraySize(js_list);
printf("array size is %d\n", array_size);
for(int i = 0; i < array_size; i++)
{
cJSON *item = cJSON_GetArrayItem(js_list, i);
printf("item type is %d\n", item->type);
printf("%s\n", item->valuestring);
}
if (root)
{
cJSON_Delete(root);
}
return 0;
}
1.7.18 版本,打印結果如下:
array size is 2
item type is 16
luffy
item type is 16
robin
4.4 解析嵌套的 Json 對象和數組
對于 Json 字符串最復雜的個數莫過于 Json 對象和 Json 數組嵌套的形式,下面通過一個例子演示一下應該如何解析,字符串格式如下:
{\"list\":[{\"name\":\"luffy\",\"age\":19},{\"name\":\"sabo\",\"age\":21}]}
在解析的時候,我們只需要按照從屬關系,一層層解析即可:
-
根節點是一個 Json 對象,基于根節點中的 key 值取出對應的 value 值,得到一個 Json 數組
-
讀出 Json 數組的大小,遍歷里邊的各個元素,每個元素都是一個 Json 對象
-
將 Json 對象中的鍵值對根據 key 值取出對應的 value 值
-
從取出的 value 值中讀出實際類型對應的數值
示例代碼如下:
#include "cJSON.h"
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
char *s = "{\"list\":[{\"name\":\"luffy\",\"age\":19},{\"name\":\"sabo\",\"age\":21}]}";
cJSON *root = cJSON_Parse(s);
if (!root)
{
printf("get root faild !\n");
return -1;
}
cJSON *list = cJSON_GetObjectItem(root, "list");
if (!list)
{
printf("no list!\n");
return -1;
}
int array_size = cJSON_GetArraySize(list);
printf("array size is %d\n", array_size);
for(int i = 0; i < array_size; i++)
{
cJSON* item = cJSON_GetArrayItem(list, i);
cJSON* name = cJSON_GetObjectItem(item, "name");
printf("name is %s\n", name->valuestring);
cJSON* age = cJSON_GetObjectItem(item, "age");
printf("age is %d\n", age->valueint);
}
if (root)
{
cJSON_Delete(root);
}
return 0;
}
1.7.18 版本,打印結果如下:
array size is 2
name is luffy
age is 19
name is sabo
age is 21

浙公網安備 33010602011771號