此文為學(xué)習(xí)知乎上“Milo的編程”專欄所載的從零開始的JSON教程所作的學(xué)習(xí)筆記绘证,同時(shí)也是學(xué)習(xí)簡(jiǎn)書的Markdown語(yǔ)法的時(shí)間个盆,因?yàn)橐恢毕胝乙粋€(gè)能做學(xué)習(xí)筆記的網(wǎng)站结胀,并且由于筆記的很多內(nèi)容涉及代碼塊沫屡,支持Markdown語(yǔ)法的簡(jiǎn)書似乎是個(gè)不錯(cuò)的選擇峡捡。同時(shí)希望自己能夠堅(jiān)持击碗,持續(xù)學(xué)習(xí)。(由于是學(xué)習(xí)筆記们拙,大量的內(nèi)容來(lái)自于專欄稍途,專欄作者為Milo Yip,特此說(shuō)明)
一砚婆、啟程
1. JSON是什么械拍?
JSON是一種用于數(shù)據(jù)交換的文本格式,具有類似功能的語(yǔ)言有XML装盯、YAML等等坷虑,但JSON的語(yǔ)法最簡(jiǎn)單。一個(gè)簡(jiǎn)單的例子的JSON文本的例子:
{
"title": "Design Patterns",
"subtitle": "Elements of Reusable Object-Oriented Software",
"author": [
"Erich Gamma",
"Richard Helm",
"Ralph Johnson",
"John Vlissides"
],
"year": 2009,
"weight": 1.8,
"hardcover": true,
"publisher": {
"Company": "Pearson Education",
"Country": "India"
},
"website": null
}
由此可以看見埂奈,一個(gè)JSON對(duì)象的表示是用{...}
迄损,JSON的數(shù)據(jù)類型有六種數(shù)據(jù)類型:
Type | Format |
---|---|
null | null |
booleab |
true or false
|
number | 浮點(diǎn)數(shù) |
string | "Design Patterns" |
array | [...] |
objecte | {} |
可以發(fā)現(xiàn)一個(gè)object的數(shù)據(jù)類型可以是另一個(gè)object,這說(shuō)明JSON是一種樹狀的結(jié)構(gòu)账磺。一個(gè)JSON庫(kù)應(yīng)當(dāng)提供的功能包括:
- parse:將JSON文本(即JSON字符串)解析為對(duì)象,肯定是一個(gè)樹狀的對(duì)象
- stringify:將一個(gè)對(duì)象文本化芹敌,即把一個(gè)對(duì)象表示為一串JSON文本
- access:提供方法使得程序中可以訪問已經(jīng)parse過的對(duì)象的數(shù)據(jù)結(jié)構(gòu)
(事實(shí)上,對(duì)于access不是特別能理解垮抗,希望在感悟下)
2. 編譯環(huán)境配置
pass
此處的內(nèi)容之后再補(bǔ)充氏捞,對(duì)于編譯環(huán)境的搭建CMake、Git的使用還是要抽時(shí)間本地使用下
3.頭文件與API設(shè)計(jì)
- 頭文件#include 防范:為了防止重復(fù)引用頭文件而采取的一種方法冒版,一般為每個(gè)頭文件中預(yù)定義一個(gè)唯一的宏液茎,然后判斷是否這個(gè)宏已經(jīng)定義過了。
#ifndef LEPTJSON_H__
#define LEPTJSON_H__
/* 此處寫入頭文件內(nèi)容辞嗡,如各種聲明 */
#endif /*LEPTJSON_H__*/
一般以_H__
作為后綴捆等,并且要保證唯一。
- 六種數(shù)據(jù)類型:將
true
和false
算作兩種類型续室,那么共有其中類型栋烤,定義一個(gè)enum類型。為了方便使用猎贴,用關(guān)鍵字typedef
在處理下:
typedef enum {
LEPT_NULL,
LEPT_FALSE,
LEPT_TRUE,
LEPT_NUMBER,
LEPT_STRING,
LEPT_ARRAY,
LEPT_OBJECT
} lept_type;
一些作者提到的注意點(diǎn):C語(yǔ)言沒有明明空間的概念,因此需要保證標(biāo)識(shí)符的唯一性,因此一般用項(xiàng)目名稱的簡(jiǎn)寫做前綴她渴,枚舉值用大寫而函數(shù)和類型定義用小寫
- JSON是一個(gè)樹狀的數(shù)據(jù)結(jié)構(gòu)达址,每個(gè)結(jié)點(diǎn)用一個(gè)結(jié)構(gòu)表示,當(dāng)前第一節(jié)只考慮三種類型
null
趁耗,false
沉唠,true
,因此就當(dāng)前來(lái)講苛败,這個(gè)結(jié)構(gòu)只需要存儲(chǔ)一個(gè)數(shù)據(jù)類型即可满葛,以下是定義:
typedef struct {
lept_type type;
} lept_value;
- API定義:
typedef enum {
LEPT_PARSE_OK ,
LEPT_PARSE_EXPECT_VALUE,
LEPT_PARSE_INVALID_VALUE,
LEPT_PARSE_ROOT_NOT_SINGULAR
} lept_parse_result;
/*API-1,解析一個(gè)json罢屈,即parse嘀韧,結(jié)果填入一個(gè)lept_value。注意const的用法是保證json不會(huì)被改動(dòng)*/
lept_parse_result lept_parse(lept_value &v, const char* json);
/*API-2缠捌,獲取結(jié)果锄贷,即access,注意const 以及指針的使用(防止對(duì)象拷貝)*/
lept_type lept_get_tyepe(const lept_value* v);
- 此單元json只包含
null
,false
,true
三種類型曼月,語(yǔ)法描述為:
json_text = ws value ws
ws = *(%0x20, %x0x09, %0x0A, %x0D) ------* 表示零或多個(gè)
value = 'null' / 'false' / 'true' -----/ 表示其中一個(gè)
前面已經(jīng)提到了lept_parse
的返回類型谊却,下面表示本單元語(yǔ)法下分別代表的含義:
typedef enum {
LEPT_PARSE_OK , //解析成功
LEPT_PARSE_EXPECT_VALUE, //JSON串只有空白
LEPT_PARSE_INVALID_VALUE,
LEPT_PARSE_ROOT_NOT_SINGULAR //一個(gè)值、空白后還有其他值
} lept_parse_result;
- TTD(測(cè)試驅(qū)動(dòng)開發(fā))
是一種開發(fā)方法哑芹,主要是:- 加入一個(gè)測(cè)試
- 運(yùn)行所有測(cè)試炎辨,新的測(cè)試會(huì)失敗
- 編寫實(shí)現(xiàn)代碼
- 運(yùn)行所有測(cè)試,如有失敗返回3
- 重構(gòu)代碼聪姿,并測(cè)試碴萧,如有失敗返回3
- 返回1
TTD是先寫測(cè)試在寫開發(fā),優(yōu)點(diǎn)是實(shí)現(xiàn)剛好滿足測(cè)試的代碼咳燕,且容易把控質(zhì)量勿决。本項(xiàng)目提供了一個(gè)測(cè)試框架。以下做代碼學(xué)習(xí)招盲。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "leptjson.h"
static int main_ret = 0;
static int test_count = 0;
static int test_pass = 0;
//注意宏分多行寫的技巧
//注意fprintf和printf區(qū)別:注意format為什么可以這么寫:類似于char * = “%d”“%d”這樣寫沒毛病低缩,注意stderr
//printf是向stdout,fprintf是向FILE*輸出曹货,stderr和stdout等都是該類型咆繁,
//stderr時(shí)另一個(gè)輸
//出流
#define EXPECT_EQ_BASE(equality, expect, actual, format) \
do {\
test_count++;\
if (equality)\
test_pass++;\
else {\
fprintf(stderr, "%s:%d: expect: " format " actual: " format "\n", __FILE__, __LINE__, expect, actual);\
main_ret = 1;\
}\
} while(0)
#define EXPECT_EQ_INT(expect, actual) EXPECT_EQ_BASE((expect) == (actual), expect, actual, "%d")
static void test_parse_null() {
lept_value v;
v.type = LEPT_TRUE;
EXPECT_EQ_INT(LEPT_PARSE_OK, lept_parse(&v, "null"));
EXPECT_EQ_INT(LEPT_NULL, lept_get_type(&v));
}
/* ... */
static void test_parse() {
test_parse_null();
/* ... */
}
int main() {
test_parse();
printf("%d/%d (%3.2f%%) passed\n", test_pass, test_count, test_pass * 100.0 / test_count);
return main_ret;
}
4. 課后習(xí)題
其他的就不關(guān)注 了,有一點(diǎn)有意思的要注意下顶籽,當(dāng)有一個(gè)函數(shù)申明為static后意味這個(gè)函數(shù)只在當(dāng)前編譯單元使用