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


      研究該 JSON 庫的由頭是由於目前開發 modb 須要支持 json 解析功能。而發現這個有意思的 項目 的地方正是在開源中國。OSChina 對該庫的描述以下:
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.
該 json 庫主要由 json.c 和 json.h 構成,惟一的外部依賴是  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
做者給出的測試代碼以下(我已經添加註釋):
/* Generate the first example JSON snippet from RFC 4627:
 *
 * {
 * 	"Image": {
 * 		"Width":  800,
 * 		"Height": 600,
 * 		"Title":  "View from 15th Floor",
 * 		"Thumbnail": {
 * 			"Url":    "http://www.example.com/image
 * 			"Height": 125,
 * 			"Width":  "100"
 * 		},
 * 		"IDs": [116, 943, 234, 38793]
 * 	}
 * }
 */
#include "json.h"

int main(void) {
	struct json *J;
	int error;

	J = json_open(JSON_F_NONE, &error);

	// 1.建立頂層object
	// 2.建立Image 爲object
	// 3.建立Thumbnail爲string其值爲null value
	// 4.將下一操做的root定位在.Image.Thumbnail上
	json_push(J, ".Image.Thumbnail");
	/* automatically instantiates . as an object, .Image as an object,
	 * and .Image.Thumbnail as a null value. .Image.Thumbnail is now the
	 * root node for path expressions.
	 */

	// 1.自動將Thumbnail從value轉變爲object
	// 2.在Thumbnail下建立Url爲string並設置其值也爲string
	json_setstring(J, "http://www.example.com/image/481989943", "Url");	/* 是否應該爲".Url" */
	/* automatically converts .Image.Thumbnail to an object and
	 * instantiates .Image.Thumbnail.Url to a string
	 */

	// 在Thumbnail下建立Height爲string並設置其值爲number
	// 在Thumbnail下建立Width爲string並設置其值爲string
	json_setnumber(J, 125, ".Height");
	json_setstring(J, "100", ".Width");

	// 切回document root
	json_pop(J);
	/* Our root node for path expressions is again the document root */

	// 在Image下建立Width爲string並設置其值爲number
	// 在Image下建立Height爲string並設置其值爲number
	// 此時root應該被定位到.Image上
	json_setnumber(J, 800, ".Image.Width");
	json_setnumber(J, 600, ".Image.Height");

	// 經過字符串插值方式在Image下建立Title爲string並設置其值爲string
	json_setstring(J, "View from 15th Floor", ".Image.$", "Title");		/* 是否應該爲".Title" */
	/* $ interpolates a string into the path expression */

	// 本代碼在生成IDs時有錯誤-- 本來應該生成在Image下卻生成到了document root下了
	// 在document root下建立IDs爲array並設置其第0個元素爲number
	json_setnumber(J, 116, ".IDs[0]");
	/* .IDs is instantiated as an array and the number 116 set to the
	 * 0th index
	 */

	// 經過數組索引插值方式設置IDs的第1個元素值爲number
	json_setnumber(J, 943, ".IDs[#]", json_count(J, ".IDs"));
	/* As an array index, # is taken as the index value. json_count
	 * returns the array size of .IDs as an int, which should be 1.     
	 *
	 * (In an object key identifier, # interpolates an integer into the
	 * string key.)
	 */

	// 將root定位在.Image.IDs[2]上
	// 設置IDs[2] 的值爲number
	// 切回document root
	// 設置IDs[3] 的值爲number
	json_push(J, ".IDs[#]", json_count(J, ".IDs"));
	json_setnumber(J, 234, ".");
	json_pop(J);
	json_setnumber(J, 38793, ".IDs[3]");

	json_printfile(J, stdout, JSON_F_PRETTY);
	/* The JSON_F_PRETTY flag instructs the composer to print one value
	 * per line, and to indent each line with tabs according to its
	 * nested level
	 */

	json_close(J);

	return 0;
}

      正如我在註釋中指出的,該示例程序其實有一點小錯誤,本來應該輸出代碼最上面給出的 json 數據的,但實際輸出的以下: html

[root@Betty examples]# ./example1 
{
        "IDs" : [
                116,
                943,
                234,
                38793
        ],
        "Image" : {
                "Height" : 600,
                "Thumbnail" : {
                        "Height" : 125,
                        "Url" : "http:\/\/www.example.com\/image\/481989943",
                        "Width" : "100"
                },
                "Title" : "View from 15th Floor",
                "Width" : 800
        }
}
[root@Betty examples]#

若想生成正確的結果,能夠作以下修正: node

#include "json.h"

int main(void) {
        struct json *J;
        int error;

        J = json_open(JSON_F_NONE, &error);

        json_push(J, ".Image.Thumbnail");
        /* automatically instantiates . as an object, .Image as an object,
         * and .Image.Thumbnail as a null value. .Image.Thumbnail is now the
         * root node for path expressions.
         */
		
        json_setstring(J, "http://www.example.com/image/481989943", ".Url");
        /* automatically converts .Image.Thumbnail to an object and
         * instantiates .Image.Thumbnail.Url to a string
         */

        json_setnumber(J, 125, ".Height");
        json_setstring(J, "100", ".Width");

        json_pop(J);
        /* Our root node for path expressions is again the document root */

        json_setnumber(J, 800, ".Image.Width");
        json_setnumber(J, 600, ".Image.Height");

        json_setstring(J, "View from 15th Floor", ".Image.$", "Title");
        /* $ interpolates a string into the path expression */

        json_push(J,".Image");
        json_setnumber(J, 116, ".IDs[0]");
        /* .IDs is instantiated as an array and the number 116 set to the
         * 0th index
         */

        json_setnumber(J, 943, ".IDs[#]", json_count(J, ".IDs"));
        /* As an array index, # is taken as the index value. json_count
         * returns the array size of .IDs as an int, which should be 1.     
         *
         * (In an object key identifier, # interpolates an integer into the
         * string key.)
         */

        json_setnumber(J, 234, ".IDs[2]");
        json_setnumber(J, 38793, ".IDs[3]");

        json_printfile(J, stdout, JSON_F_PRETTY);
        /* The JSON_F_PRETTY flag instructs the composer to print one value
         * per line, and to indent each line with tabs according to its
         * nested level
         */

        json_close(J);

        return 0;
}

此時的輸出結果爲: shell

[root@Betty examples]# ./example1 
{
        "Image" : {
                "Height" : 600,
                "IDs" : [
                        116,
                        943,
                        234,
                        38793
                ],
                "Thumbnail" : {
                        "Height" : 125,
                        "Url" : "http:\/\/www.example.com\/image\/481989943",
                        "Width" : "100"
                },
                "Title" : "View from 15th Floor",
                "Width" : 800
        }
}
[root@Betty examples]#

這回徹底正確了,V5!! express

      另外,還有一個名字 splice 的測試小程序,其實現了將標準輸入或者文件做爲數據源,進行信息提取後展示到標準輸出的功能。這裏再也不進行源碼解讀,給出運行結果供參考。
[root@Betty examples]# cat json_test.txt 
{
        "Image" : {
                "Height" : 600,
                "IDs" : [
                        116,
                        943,
                        234,
                        38793
                ],
                "Thumbnail" : {
                        "Height" : 125,
                        "Url" : "http:\/\/www.example.com\/image\/481989943",
                        "Width" : "100"
                },
                "Title" : "View from 15th Floor",
                "Width" : 800
        }
}
[root@Betty examples]# 
[root@Betty examples]# ./splice -h
splice [-Vh] to-file to-path from-file [from-path]
  -V  print version
  -h  print usage

Report bugs to <william@25thandClement.com>
[root@Betty examples]# 
[root@Betty examples]# ./splice json_test_2.txt . json_test.txt 
{
        "Image" : {
                "Height" : 600,
                "IDs" : [
                        116,
                        943,
                        234,
                        38793
                ],
                "Thumbnail" : {
                        "Height" : 125,
                        "Url" : "http:\/\/www.example.com\/image\/481989943",
                        "Width" : "100"
                },
                "Title" : "View from 15th Floor",
                "Width" : 800
        }
}
[root@Betty examples]# 
[root@Betty examples]# ./splice json_test_2.txt . json_test.txt Image
{
        "Height" : 600,
        "IDs" : [
                116,
                943,
                234,
                38793
        ],
        "Thumbnail" : {
                "Height" : 125,
                "Url" : "http:\/\/www.example.com\/image\/481989943",
                "Width" : "100"
        },
        "Title" : "View from 15th Floor",
        "Width" : 800
}
[root@Betty examples]# 
[root@Betty examples]# ./splice json_test_2.txt moooofly json_test.txt Image 
{
        "moooofly" : {
                "Height" : 600,
                "IDs" : [
                        116,
                        943,
                        234,
                        38793
                ],
                "Thumbnail" : {
                        "Height" : 125,
                        "Url" : "http:\/\/www.example.com\/image\/481989943",
                        "Width" : "100"
                },
                "Title" : "View from 15th Floor",
                "Width" : 800
        }
}
[root@Betty examples]# 
[root@Betty examples]# ./splice json_test_2.txt moooofly json_test.txt Title
{
        "moooofly" : null
}
[root@Betty examples]#
相關文章
相關標籤/搜索