【原创】具备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]#
相关文章
相关标签/搜索