【原创】具有path autovivification和conversion功能的JSON库

  1. 云栖社区>
  2. 博客>
  3. 正文

【原创】具有path autovivification和conversion功能的JSON库

摩云飞 2016-05-11 14:15:02 浏览819 评论0

摘要:       研究该 JSON 库的由头是因为目前开发 modb 需要支持 json 解析功能。而发现这个有意思的 项目 的地方正是在开源中国。OSChina 对该库的描述如下: json.c 是一个小型的 C 语言的 JSON 解析库,支持路径表达式、autovivification, 和 restartable I/O.


      研究该 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
作者给出的测试代码如下(我已经添加注释): 
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
/* 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 数据的,但实际输出的如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[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]#

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

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
#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;
}

此时的输出结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[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!!

      另外,还有一个名字 splice 的测试小程序,其实现了将标准输入或者文件作为数据源,进行信息提取后展现到标准输出的功能。这里不再进行源码解读,给出运行结果供参考。
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
[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]#

【云栖快讯】阿里巴巴小程序繁星计划,20亿补贴第一弹云应用免费申请,限量从速!  详情请点击

网友评论