開源專案cJSON具體實現2(數字的解析)

2021-09-27 21:13:00 字數 4483 閱讀 3237

2.1 json number 的語法規則與解釋。

json number 的語法規則是這樣的:

/*

number 以十進位制表示,它主要由 4 部分組成:負號、整數、小數、指數。只有整數是必需部分。注意:+號是不合法的。

int 整數部分如果是 0 開始,只能是單個 0;而由 1-9 開始的話,可以加任意數量的數字(0-9)。也就是說,0123 不是乙個合法的 json 數字。

frac 小數部分比較直觀,就是小數點後是一或多個數字(0-9)。

json 可使用科學記數法,exp 指數部分由大寫 e 或小寫 e 開始,然後可有正負號,之後是一或多個數字(0-9)。

*/json

-text = ws value ws

ws =*(

%x20 /

%x09 /

%x0a /

%x0d)

value = number

number =

["-"

] int [ frac ]

[ exp ]

int =

"0"/ digit1-

9*digit

frac =

"."1

*digit

exp =

("e"

/"e")[

"-"/

"+"]

1*digit

json 標準 ecma-404 採用圖的形式表示語法,也可以更直觀地看到解析時可能經過的路徑:

2.2 設計標頭檔案

json 是乙個樹形結構,我們設定每個節點使用結構體 lept_value表示,對於null、false、true 在解析後,我們只需要儲存它的資料型別為lept_null、lept_false、lept_true;但對於數字,不僅要儲存結點的型別,還要儲存資料本身。

從 json 數字的語法,我們可能直觀地會認為它應該表示為乙個浮點數,因為它帶有小數和指數部分。然而,標準中並沒有限制數字的範圍或精度。為簡單起見,選擇以雙精度浮點數來儲存 json 數字。

所以,此時 lept_value 組成就為:

typedef

struct

lept_value;

api設計

/*

函式目的:獲取json結點裡的數值

引數:1. lept_value* v :傳入儲存解析後json樹形結構的根節點指標

返回值:double

*/double

lept_get_number

(const lept_value* v)

;

api 實現

/*

函式目的:獲取json結點裡的數值

引數:1. lept_value* v :傳入儲存解析後json樹形結構的根節點指標

返回值:double

實現思路:僅當 type == lept_number時,才返回數值

*/double

lept_get_number

(const lept_value* v)

2.3 tdd設計理念

先寫測試函式,寫測試函式時考慮的情況越多,最後在設計解析器的時候就越完善。

#define expect_eq_double(expect, actual) expect_eq_base((expect) == (actual),expect,actual, "%.17g")  

#define test_number(expect, json)\

do while(0)

static

void

test_parse_number()

除了這些上面這些合法的測試用例,根據json數字解析規則我們來設計一些不合語法的用例:

#define test_error(error, json, lept_type)\

do while (0);

static

void

test_parse_invalid_value()

static

void

test_parse_root_not_singular()

static

void

test_parse_number_too_big()

整數部分全為1,小數部分全為0表示無窮大,即inf。根據符號位的不同可以分為+inf / -inf

整數部分全為1,小數部分不全為0表示nan。即not a number。

小數部分最高位為1的nan稱為qnan;最高位為0的nan稱為snan。通常用qnan表示不確定的操作,snan表示無效的操作。

浮點數0000 0000 0000 0001(十六進製制表示法)的小數部分(後13位)是0 0000 0000 0001,即1/252, 對應的數是1/(252) *2(-1022),即4.9406564584124654e-324

2.4 實現json_number解析器

根據 api 與 單元測試 ,我們來實現解析器,首先是lept_parse函式

static

intlept_parse_value

(lept_context* c, lept_value* v)}/*

注:這個函式因為邏輯沒有變,所以本身不用改變,需要改變的是它所呼叫的 lept_parse_value 函式。

*/int

lept_parse

(lept_value* v,

const

char

* json)

根據json的數字規則寫 lept_parse_number()

#include

/* errno, erange */

#include

/* huge_val */

#include

/* null, strtod() */

#define isdigit(ch) ((ch) >= '0' && (ch) <= '9')

#define isdigit1to9(ch) ((ch) >= '1' && (ch) <= '9')

/*函式目的: 對number進行解析

思路:1. 判斷是不是負號,是:跳過

2. 整數部分:判斷是否為單個0的情況,是便跳過;如果不是第一種情況,則整數部分第乙個字元必須為1~9,否則返回錯誤碼;然後有多少個digit就跳過多少。

3. 小數部分:如果出現小數點,我們跳過該小數點,然後檢查它至少應有乙個 digit,不是 digit 就返回錯誤碼。跳過首個 digit,我們再檢查有沒有 digit,有多少個跳過多少個。

4. 指數部分:如果出現大小寫 e,就表示有指數部分。跳過那個 e 之後,可以有乙個正或負號,有的話就跳過。然後和小數的邏輯是一樣的

*/static

intlept_parse_number

(lept_context* c, lept_value* v)

/* 小數 ... */if(

*p ==

'.')

/* 指數 ... */if(

*p ==

'e'||

*p ==

'e')

errno =0;

v->n =

strtod

(c->json,

null);

if(errno == erange &&

(v->n == huge_val || v->n ==

-huge_val)

)return lept_parse_number_too_big;

v->type = lept_number;

c->json = p;

return lept_parse_ok;

}

上面的**,要再說兩點:

strtod函式,將字串轉換成浮點數,這個函式的定義是 double strtod(const char str, char

**endprt) 其中 str為要轉化為雙精度浮點數的字串; endptr對char的物件的引用,其值由函式設定為str中數值後的下乙個字元。

在math.h中, 當函式的結果不可以表示為浮點數時。如果是因為結果的幅度太大以致於無法表示,則函式會設定 errno 為 erange 來表示範圍錯誤,並返回乙個由巨集 huge_val 或者它的否定(- huge_val)命名的乙個特定的很大的值; 如果結果的幅度太小,則會返回零值。在這種情況下,error 可能會被設定為 erange,也有可能不會被設定為 erange。

linux開源專案 cJSON

cjson是c語言中的乙個json編解碼器,非常輕量級,c檔案只有500多行,速度也非常理想。cjson也存在幾個弱點,雖然功能不是非常強大,但cjson的小身闆和速度是最值得讚賞的。其 被非常好地維護著,結構也簡單易懂,可以作為乙個非常好的c語言專案進行學習。專案主頁 json介紹 英文 中文 j...

Github上的開源專案2

krvideoplayer kxmovie vkvideoplayer ijkplayer android ios video player based on ffmpeg n2.8,with mediacodec,videotoolbox support.eleven eleven player ...

開源雷射SLAM專案BLAM 2

接上一章節提到的 processpointcloudmessage m msg 函式,它傳入乙個const pointcloud constptr 型別,即點雲常指標的引用,程式源 如下 void blamslam processpointcloudmessage const pointcloud ...