天天看點

【原創】具有path autovivification和conversion功能的JSON庫

json.c 是一個小型的 c 語言的 json 解析庫,支援路徑表達式、autovivification, 和 restartable i/o.

而庫的作者做了更為豐富的表述(中英對照翻譯如下): 

===== 

json.c is a json c library that supports path autovivification and conversion. autovivified and converted paths greatly simplify manipulation of json trees, allowing one to discard most bothersome boilerplate code. 

json.c 作為 json 的 c 庫實作,支援  path autovivification 和   conversion 功能。這兩項功能極大的簡化了針對 json 樹結構 上的各種操作,允許你在代碼編寫過程中避免大量讓人讨厭的“樣闆”代碼。 

because "json schema" is something of an oxymoron, the library makes the presumption that you mean what you say; that the schema is whatever the code does. if you attempt to write through a non-existent or type-incompatible path, the library fixes the tree to accomodate the request, rather than punt back to the caller. likewise, a read through a non-existent or type-incompatible path returns a sane, default value—specifically 0 or an empty string. 

因為“json schema”是一個容易讓人搞不清楚的東西,是以在該庫的實作中作了如下假定:你怎樣描述就是怎樣的結果;代碼的動作決定 schema 的模樣。如果你企圖在一個不存在,或者類型不相容的 json 路徑上寫穿(可以了解為強寫),該庫會按照你的 request 對樹結構進行相應的修正,而不是什麼都不幹就傳回到調用者處。同樣地,對一個不存在的,或者類型不相容的 json 路徑進行讀穿,将會得到恒定不變的預設值 - 一般是 0 或者空字串。 

in addition, a stack interface allows changing the current root. this makes descending up and down the tree more convenient, and eases abstraction as code which reads or writes subtrees need not be concerned with the path to that particular subtree. 

另外,通過 stack interface 可以友善地變更目前的 root 位置。這也使得在樹結構上進行上下移動變得更加容易,并提供了更為簡單的代碼級抽象 -- 在讀或者寫子樹結構時無需關心與該子樹對應的路徑。 

both the parser and composer are restartable and operate over a series of caller provided input and output buffers. this is intended to ease integration with networked i/o environments, particularly non-blocking environments. json data can be both parsed and composed byte-by-byte without any intermediate, internal buffers. no callback schemes are used as half measures, so the user code needn't arbitrarily split blocks of code between different halves of a parse or compose operation. 

解析器和生成器均具有 restartable 的特點,可以同時為不止一個調用者提供輸入輸出緩沖。這個設計的目的是為了簡化在網絡 i/o 環境應用時的內建工作,特别是用于非阻塞環境下。json 資料可以按逐位元組方式解析和生成,并且無需任何中間或者内部緩沖。回調處理被設計成不會在 json 資料解析和生成過程中同時觸發,故使用者代碼塊的執行不會是以出現從解析部分變換到生成部分的過程,反之亦然。 

json.c is implemented in a single source file, along with a companion header which defines the api. the only dependency is llrb.h. 

the api is broken up into three main sections. the grouping of declarations in the header reflects this, and makes for relatively easy perusal. 

api 主要分成 3 個主要部分。可以從頭檔案中的分組聲明看出。 

the core api consists of routines to construct a json context object, and to parse and compose json data. 

核心 api 包括:構造 json 上下文對象的 api、解析 json 資料的 api、生成 json 資料的 api。 

the second set consists of routines to manipulate json value objects directly, although this is mostly used internally. 

第二部分包括:直接操作 json value 對象的 api(大部分情況下僅在庫内部調用)。 

the third consists of routines which access and manipulate the json tree through a path expression. objects are accessed with a dot ("."), arrays indexed by square brackets ("[", "]"). string interpolation is accomplished using the special character "$" and index interpolation using "#", along with their respective arguments (char pointer or int) in the passed variable argument list. the syntax uses the standard backslash escaping protocol. 

第三部分包括:通過路徑表達式通路和操作 json 樹結構的 api。object 的通路通過點操作符(“.”);array 的索引通過中括号實作(“[”,“]”);字元串内的插值操作使用特殊字元“$”;array 索引的插值操作使用特殊字元“#”。最後兩個插值操作需要同時提供相應的參數清單(字元串指針或者整形變量 )。整體的文法規則使用了标準的反斜杠轉義協定。 

the core api returns errors directly, while most of the remainder signal errors using longjmp(). the macro pair json_enter()/json_leave() are analogous to try/finally. however, thrown errors need not be caught; aren't thrown unless an exception context is provided; consistency is maintained if errors are ignored; and a sane and safe value always returned when reading a value. json.c error codes are negative numbers in the range json_ebase to json_elast, and communicated along with system error codes through a single int value. posix guarantees system codes to be positive. 

在設計上,核心 api 會直接傳回錯誤資訊,而其他大部分 api 是采用 longjmp() 方式來通知錯誤的發生。json_enter()/json_leave() 這對宏功能上類似與 try/finally 。然而,抛出的錯誤是不需要進行捕獲的;僅在具有異常上下文的時候抛出異常;在錯誤資訊被忽略的情況下能夠保證資料的一緻性;在讀值操作中總能保證傳回一個不會變化的安全值。json.c 中的錯誤碼是範圍在 json_ebase 到 json_elast 之間的負數。 

a small regression utility can be built from the source. defining json_main will expose the main() definition, and until documentation can be written usage can be gleaned from the implementation of the utility commands. the utility requires libffi in order to dynamically construct calls with interpolating path expressions, useful for exercising path evaluation. however, the include path for the libffi header is hit-and-miss. and a bug report has been filed with apple because clang chokes on their broken ffi.h header. 

小型的回歸測試類應用可以直接在該源檔案的基礎上進行。 

=====

該庫最大的特點(上面均有相應的解釋): 

autovivification

conversion

restartable

作者給出的測試代碼如下(我已經添加注釋): 

<a href="http://my.oschina.net/moooofly/blog/180186#">?</a>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

<code>/* generate the first example json snippet from rfc 4627:</code>

<code> </code><code>*</code>

<code> </code><code>* {</code>

<code> </code><code>*  "image": {</code>

<code> </code><code>*      "width":  800,</code>

<code> </code><code>*      "height": 600,</code>

<code> </code><code>*      "title":  "view from 15th floor",</code>

<code> </code><code>*      "thumbnail": {</code>

<code> </code><code>*          "url":    "http://www.example.com/image</code>

<code> </code><code>*          "height": 125,</code>

<code> </code><code>*          "width":  "100"</code>

<code> </code><code>*      },</code>

<code> </code><code>*      "ids": [116, 943, 234, 38793]</code>

<code> </code><code>*  }</code>

<code> </code><code>* }</code>

<code> </code><code>*/</code>

<code>#include "json.h"</code>

<code>int</code> <code>main(</code><code>void</code><code>) {</code>

<code>    </code><code>struct</code> <code>json *j;</code>

<code>    </code><code>int</code> <code>error;</code>

<code>    </code><code>j = json_open(json_f_none, &amp;error);</code>

<code>    </code><code>// 1.建立頂層object</code>

<code>    </code><code>// 2.建立image 為object</code>

<code>    </code><code>// 3.建立thumbnail為string其值為null value</code>

<code>    </code><code>// 4.将下一操作的root定位在.image.thumbnail上</code>

<code>    </code><code>json_push(j,</code><code>".image.thumbnail"</code><code>);</code>

<code>    </code><code>/* automatically instantiates . as an object, .image as an object,</code>

<code>     </code><code>* and .image.thumbnail as a null value. .image.thumbnail is now the</code>

<code>     </code><code>* root node for path expressions.</code>

<code>     </code><code>*/</code>

<code>    </code><code>// 1.自動将thumbnail從value轉變為object</code>

<code>    </code><code>// 2.在thumbnail下建立url為string并設定其值也為string</code>

<code>    </code><code>json_setstring(j,</code><code>"http://www.example.com/image/481989943"</code><code>,</code><code>"url"</code><code>);</code><code>/* 是否應該為".url" */</code>

<code>    </code><code>/* automatically converts .image.thumbnail to an object and</code>

<code>     </code><code>* instantiates .image.thumbnail.url to a string</code>

<code>    </code><code>// 在thumbnail下建立height為string并設定其值為number</code>

<code>    </code><code>// 在thumbnail下建立width為string并設定其值為string</code>

<code>    </code><code>json_setnumber(j, 125,</code><code>".height"</code><code>);</code>

<code>    </code><code>json_setstring(j,</code><code>"100"</code><code>,</code><code>".width"</code><code>);</code>

<code>    </code><code>// 切回document root</code>

<code>    </code><code>json_pop(j);</code>

<code>    </code><code>/* our root node for path expressions is again the document root */</code>

<code>    </code><code>// 在image下建立width為string并設定其值為number</code>

<code>    </code><code>// 在image下建立height為string并設定其值為number</code>

<code>    </code><code>// 此時root應該被定位到.image上</code>

<code>    </code><code>json_setnumber(j, 800,</code><code>".image.width"</code><code>);</code>

<code>    </code><code>json_setnumber(j, 600,</code><code>".image.height"</code><code>);</code>

<code>    </code><code>// 通過字元串插值方式在image下建立title為string并設定其值為string</code>

<code>    </code><code>json_setstring(j,</code><code>"view from 15th floor"</code><code>,</code><code>".image.$"</code><code>,</code><code>"title"</code><code>);    </code><code>/* 是否應該為".title" */</code>

<code>    </code><code>/* $ interpolates a string into the path expression */</code>

<code>    </code><code>// 本代碼在生成ids時有錯誤-- 原本應該生成在image下卻生成到了document root下了</code>

<code>    </code><code>// 在document root下建立ids為array并設定其第0個元素為number</code>

<code>    </code><code>json_setnumber(j, 116,</code><code>".ids[0]"</code><code>);</code>

<code>    </code><code>/* .ids is instantiated as an array and the number 116 set to the</code>

<code>     </code><code>* 0th index</code>

<code>    </code><code>// 通過數組索引插值方式設定ids的第1個元素值為number</code>

<code>    </code><code>json_setnumber(j, 943,</code><code>".ids[#]"</code><code>, json_count(j,</code><code>".ids"</code><code>));</code>

<code>    </code><code>/* as an array index, # is taken as the index value. json_count</code>

<code>     </code><code>* returns the array size of .ids as an int, which should be 1.    </code>

<code>     </code><code>*</code>

<code>     </code><code>* (in an object key identifier, # interpolates an integer into the</code>

<code>     </code><code>* string key.)</code>

<code>    </code><code>// 将root定位在.image.ids[2]上</code>

<code>    </code><code>// 設定ids[2] 的值為number</code>

<code>    </code><code>// 設定ids[3] 的值為number</code>

<code>    </code><code>json_push(j,</code><code>".ids[#]"</code><code>, json_count(j,</code><code>".ids"</code><code>));</code>

<code>    </code><code>json_setnumber(j, 234,</code><code>"."</code><code>);</code>

<code>    </code><code>json_setnumber(j, 38793,</code><code>".ids[3]"</code><code>);</code>

<code>    </code><code>json_printfile(j, stdout, json_f_pretty);</code>

<code>    </code><code>/* the json_f_pretty flag instructs the composer to print one value</code>

<code>     </code><code>* per line, and to indent each line with tabs according to its</code>

<code>     </code><code>* nested level</code>

<code>    </code><code>json_close(j);</code>

<code>    </code><code>return</code> <code>0;</code>

<code>}</code>

      正如我在注釋中指出的,該示例程式其實有一點小錯誤,原本應該輸出代碼最上面給出的 json 資料的,但實際輸出的如下:

<code>[root@betty examples]</code><code># ./example1</code>

<code>{</code>

<code>        </code><code>"ids"</code> <code>: [</code>

<code>                </code><code>116,</code>

<code>                </code><code>943,</code>

<code>                </code><code>234,</code>

<code>                </code><code>38793</code>

<code>        </code><code>],</code>

<code>        </code><code>"image"</code> <code>: {</code>

<code>                </code><code>"height"</code> <code>: 600,</code>

<code>                </code><code>"thumbnail"</code> <code>: {</code>

<code>                        </code><code>"height"</code> <code>: 125,</code>

<code>                        </code><code>"url"</code> <code>:</code><code>"http:\/\/www.example.com\/image\/481989943"</code><code>,</code>

<code>                        </code><code>"width"</code> <code>:</code><code>"100"</code>

<code>                </code><code>},</code>

<code>                </code><code>"title"</code> <code>:</code><code>"view from 15th floor"</code><code>,</code>

<code>                </code><code>"width"</code> <code>: 800</code>

<code>        </code><code>}</code>

<code>[root@betty examples]</code><code>#</code>

若想生成正确的結果,可以做如下修正:

<code>        </code><code>struct</code> <code>json *j;</code>

<code>        </code><code>int</code> <code>error;</code>

<code>        </code><code>j = json_open(json_f_none, &amp;error);</code>

<code>        </code><code>json_push(j,</code><code>".image.thumbnail"</code><code>);</code>

<code>        </code><code>/* automatically instantiates . as an object, .image as an object,</code>

<code>         </code><code>* and .image.thumbnail as a null value. .image.thumbnail is now the</code>

<code>         </code><code>* root node for path expressions.</code>

<code>         </code><code>*/</code>

<code>        </code> 

<code>        </code><code>json_setstring(j,</code><code>"http://www.example.com/image/481989943"</code><code>,</code><code>".url"</code><code>);</code>

<code>        </code><code>/* automatically converts .image.thumbnail to an object and</code>

<code>         </code><code>* instantiates .image.thumbnail.url to a string</code>

<code>        </code><code>json_setnumber(j, 125,</code><code>".height"</code><code>);</code>

<code>        </code><code>json_setstring(j,</code><code>"100"</code><code>,</code><code>".width"</code><code>);</code>

<code>        </code><code>json_pop(j);</code>

<code>        </code><code>/* our root node for path expressions is again the document root */</code>

<code>        </code><code>json_setnumber(j, 800,</code><code>".image.width"</code><code>);</code>

<code>        </code><code>json_setnumber(j, 600,</code><code>".image.height"</code><code>);</code>

<code>        </code><code>json_setstring(j,</code><code>"view from 15th floor"</code><code>,</code><code>".image.$"</code><code>,</code><code>"title"</code><code>);</code>

<code>        </code><code>/* $ interpolates a string into the path expression */</code>

<code>        </code><code>json_push(j,</code><code>".image"</code><code>);</code>

<code>        </code><code>json_setnumber(j, 116,</code><code>".ids[0]"</code><code>);</code>

<code>        </code><code>/* .ids is instantiated as an array and the number 116 set to the</code>

<code>         </code><code>* 0th index</code>

<code>        </code><code>json_setnumber(j, 943,</code><code>".ids[#]"</code><code>, json_count(j,</code><code>".ids"</code><code>));</code>

<code>        </code><code>/* as an array index, # is taken as the index value. json_count</code>

<code>         </code><code>* returns the array size of .ids as an int, which should be 1.    </code>

<code>         </code><code>*</code>

<code>         </code><code>* (in an object key identifier, # interpolates an integer into the</code>

<code>         </code><code>* string key.)</code>

<code>        </code><code>json_setnumber(j, 234,</code><code>".ids[2]"</code><code>);</code>

<code>        </code><code>json_setnumber(j, 38793,</code><code>".ids[3]"</code><code>);</code>

<code>        </code><code>json_printfile(j, stdout, json_f_pretty);</code>

<code>        </code><code>/* the json_f_pretty flag instructs the composer to print one value</code>

<code>         </code><code>* per line, and to indent each line with tabs according to its</code>

<code>         </code><code>* nested level</code>

<code>        </code><code>json_close(j);</code>

<code>        </code><code>return</code> <code>0;</code>

此時的輸出結果為:

<code>                </code><code>"ids"</code> <code>: [</code>

<code>                        </code><code>116,</code>

<code>                        </code><code>943,</code>

<code>                        </code><code>234,</code>

<code>                        </code><code>38793</code>

<code>                </code><code>],</code>

這回完全正确了,v5!!

      另外,還有一個名字 splice 的測試小程式,其實作了将标準輸入或者檔案作為資料源,進行資訊提取後展現到标準輸出的功能。這裡不再進行源碼解讀,給出運作結果供參考。

<code>[root@betty examples]</code><code># cat json_test.txt</code>

<code>[root@betty examples]</code><code># ./splice -h</code>

<code>splice [-vh] to-</code><code>file</code> <code>to-path from-</code><code>file</code> <code>[from-path]</code>

<code>  </code><code>-v  print version</code>

<code>  </code><code>-h  print usage</code>

<code>report bugs to &lt;[email protected]&gt;</code>

<code>[root@betty examples]</code><code># ./splice json_test_2.txt . json_test.txt</code>

<code>[root@betty examples]</code><code># ./splice json_test_2.txt . json_test.txt image</code>

<code>        </code><code>"height"</code> <code>: 600,</code>

<code>        </code><code>"thumbnail"</code> <code>: {</code>

<code>                </code><code>"height"</code> <code>: 125,</code>

<code>                </code><code>"url"</code> <code>:</code><code>"http:\/\/www.example.com\/image\/481989943"</code><code>,</code>

<code>                </code><code>"width"</code> <code>:</code><code>"100"</code>

<code>        </code><code>},</code>

<code>        </code><code>"title"</code> <code>:</code><code>"view from 15th floor"</code><code>,</code>

<code>        </code><code>"width"</code> <code>: 800</code>

<code>[root@betty examples]</code><code># ./splice json_test_2.txt moooofly json_test.txt image</code>

<code>        </code><code>"moooofly"</code> <code>: {</code>

<code>[root@betty examples]</code><code># ./splice json_test_2.txt moooofly json_test.txt title</code>

<code>        </code><code>"moooofly"</code> <code>: null</code>

<code></code>