riffraff_jsonpath 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,106 @@
1
+ grammar JSONPathGrammar
2
+
3
+ rule path
4
+ root selectors:child+ <JSONPath::Nodes::RootNode>
5
+ end
6
+
7
+ rule root
8
+ '$'
9
+ end
10
+
11
+ rule number
12
+ '-'? [0-9]+
13
+ end
14
+
15
+ rule wildcard
16
+ '*' / '\'' '*' '\''
17
+ end
18
+
19
+ rule lower
20
+ descendant / '.'
21
+ end
22
+
23
+ rule descendant
24
+ '..'
25
+ end
26
+
27
+ rule word_list
28
+ quoted_word ',' ' '* word_list
29
+ /
30
+ quoted_word
31
+ end
32
+
33
+ rule quoted_word
34
+ '\'' key:(!'\'' . )+ '\''
35
+ end
36
+
37
+ rule child
38
+ # .foo or ..foo
39
+ lower key:bareword <JSONPath::Nodes::KeyNode>
40
+ /
41
+ # .* or ..*
42
+ lower wildcard <JSONPath::Nodes::WildcardNode>
43
+ /
44
+ # '?(@ % 2 == 0)'
45
+ lower '\'?(' template_code:(!')\'' . )+ ')\'' <JSONPath::Nodes::FilterNode>
46
+ /
47
+ # .'foo'
48
+ lower '\'' key:(!'\'' . )+ '\'' <JSONPath::Nodes::KeyNode>
49
+ /
50
+ # [*] and ['*']
51
+ '[' wildcard ']' <JSONPath::Nodes::WildcardNode>
52
+ /
53
+ # [0]
54
+ '[' index:number ']' <JSONPath::Nodes::IndexNode>
55
+ /
56
+ # .[0] and ..[0]
57
+ lower '[' index:number ']' <JSONPath::Nodes::IndexNode>
58
+ /
59
+ # [1:2]
60
+ '[' start:number ':' stop:number ']' <JSONPath::Nodes::SliceNode>
61
+ /
62
+ # .[1:2] and ..[1:2]
63
+ lower '[' start:number ':' stop:number ']' <JSONPath::Nodes::SliceNode>
64
+ /
65
+ # [1:]
66
+ '[' start:number ':]' <JSONPath::Nodes::SliceNode>
67
+ /
68
+ # .[1:] and ..[1:]
69
+ lower '[' start:number ':]' <JSONPath::Nodes::SliceNode>
70
+ /
71
+ # [1:4:2]
72
+ '[' start:number ':' stop:number ':' step:number ']' <JSONPath::Nodes::SliceNode>
73
+ /
74
+ # .[1:4:2] and ..[1:4:2]
75
+ lower '[' start:number ':' stop:number ':' step:number ']' <JSONPath::Nodes::SliceNode>
76
+ /
77
+ # [1::2]
78
+ '[' start:number '::' step:number ']' <JSONPath::Nodes::SliceNode>
79
+ /
80
+ # .[1::2] and ..[1::2]
81
+ lower '[' start:number '::' step:number ']' <JSONPath::Nodes::SliceNode>
82
+ /
83
+ # [(@.length - 1)]
84
+ '[(' template_code:(!')]' . )+ ')]' <JSONPath::Nodes::ExprNode>
85
+ /
86
+ # .[(@.length - 1)] and ..[(@.length - 1)]
87
+ lower '[(' template_code:(!')]' . )+ ')]' <JSONPath::Nodes::ExprNode>
88
+ /
89
+ # [?(@ % 2 == 0)]
90
+ '[?(' template_code:(!')]' . )+ ')]' <JSONPath::Nodes::FilterNode>
91
+ /
92
+ # .[?(@ % 2 == 0)] and ..[?(@ % 2 == 0)]
93
+ lower '[?(' template_code:(!')]' . )+ ')]' <JSONPath::Nodes::FilterNode>
94
+ /
95
+ # ['foo']
96
+ '[' word_list ']' <JSONPath::Nodes::KeyNode>
97
+ /
98
+ # .['foo'] and ..['foo]
99
+ lower '[' word_list ']' <JSONPath::Nodes::KeyNode>
100
+ end
101
+
102
+ rule bareword
103
+ [_a-zA-Z0-9]+
104
+ end
105
+
106
+ end
@@ -0,0 +1,199 @@
1
+ require File.dirname(__FILE__) << "/test_helper"
2
+
3
+ # The content below was taken from the tests for the JS and PHP
4
+ # reference implementations at http://code.google.com/p/jsonpath/
5
+ class ParserTest < Test::Unit::TestCase
6
+
7
+ context 'Traversing' do
8
+ context "hash" do
9
+ should "handle underscore in field names" do
10
+ path = '$.a_b'
11
+ assert_resolves({"a_b" => 1}, path, [1])
12
+ end
13
+ should "parse bareword child with single terminal" do
14
+ path = '$.a'
15
+ assert_resolves({"a" => 1}, path, [1])
16
+ end
17
+ should "parse subscripted quoted child with single terminal" do
18
+ path = "$['a b']"
19
+ assert_resolves({"a b" => 1}, path, [1])
20
+ end
21
+ should "parse subscripted quoted child and chained bareword" do
22
+ path = "$['a b'].c"
23
+ assert_resolves({"a b" => {"c" => 1}}, path, [1])
24
+ end
25
+ should "parses bare wildcard" do
26
+ path = "$.*"
27
+ assert_resolves({"a" => 1, "b" => 2}, path, [1, 2])
28
+ end
29
+ should "parse quoted wildcard on hash" do
30
+ path = "$['*']"
31
+ assert_resolves({"a" => 1, "b" => 2}, path, [1, 2])
32
+ end
33
+ should "parse quoted key outside of brackets" do
34
+ path = "$.'a b'"
35
+ assert_resolves({"a b" => 1}, path, [1])
36
+ end
37
+ end
38
+ context "array" do
39
+ should "parse bare wildcard" do
40
+ path = "$.*"
41
+ assert_resolves([1, 2, 3], path, [1, 2, 3])
42
+ end
43
+ should "parses subscripted quoted wildcard" do
44
+ path = "$['*']"
45
+ assert_resolves([1, 2, 3], path, [1, 2, 3])
46
+ end
47
+ should "parses through bare wildcard" do
48
+ path = "$.*.name"
49
+ assert_resolves([{"name" => 1}, {"name" => 2}, {"name" => 3}], path, [1, 2, 3])
50
+ end
51
+ should "parse index to single terminal" do
52
+ path = "$[1]"
53
+ assert_resolves(%w(foo bar baz), path, %w(bar))
54
+ end
55
+ should "parse index to multiple terminals" do
56
+ path = "$.*[1].name"
57
+ assert_resolves({
58
+ "a" => [1, {"name" => 2}, 3],
59
+ "b" => [4, {"name" => 5}, 6],
60
+ "c" => [7, {"name" => 8}, 9],
61
+ }, path, [2, 5, 8])
62
+ end
63
+ should "find all descendants in a array selection" do
64
+ path = "$.[0]..name"
65
+ assert_resolves([[
66
+ {'name'=>'joe'},
67
+ {'name'=>'jean'},
68
+ {'name'=>'jane'}]],
69
+ path, ['joe', 'jean', 'jane'])
70
+ path = "$.ary[0]..name"
71
+ assert_resolves({
72
+ "ary"=>[[
73
+ {'name'=>'joe'},
74
+ {'name'=>'jean'},
75
+ {'name'=>'jane'}]]},
76
+ path, ['joe', 'jean', 'jane'])
77
+ end
78
+ end
79
+ context "combination wildcards" do
80
+ should "parses through bare wildcard on array with additional wildcard" do
81
+ path = "$.*.names.*"
82
+ assert_resolves([
83
+ {"names" => %w(foo bar)},
84
+ {"names" => %w(baz quux)},
85
+ {"names" => %w(spam eggs)}
86
+ ], path, %w(foo bar baz quux spam eggs))
87
+ end
88
+ end
89
+ context "using slices" do
90
+ setup {
91
+ @deep = [
92
+ {"a" => {"name" => "a1"}, "b" => {"name" => "b1"}},
93
+ {"c" => {"name" => "c1"}, "d" => {"name" => "d1"}},
94
+ {"e" => {"name" => "e1"}, "f" => {"name" => "f1"}},
95
+ {"g" => {"name" => "g1"}, "h" => {"name" => "h1"}},
96
+ {"i" => {"name" => "i1"}, "j" => {"name" => "j1"}},
97
+ {"k" => {"name" => "k1"}, "l" => {"name" => "l1"}},
98
+ {"m" => {"name" => "m1"}, "n" => {"name" => "n1"}},
99
+ ]
100
+ @shallow = [1, 2, 3, 4, 5, 6]
101
+ }
102
+ context "with explicit start and stop" do
103
+ context "with implicit step" do
104
+ should "parse to single terminal" do
105
+ path = '$[2:4]'
106
+ assert_resolves(@shallow, path, [3, 4, 5])
107
+ end
108
+ should "parse to multiple terminals" do
109
+ path = '$[2:4].*.name'
110
+ assert_resolves(@deep, path, %w(e1 f1 g1 h1 i1 j1))
111
+ end
112
+ end
113
+ context "with explicit step" do
114
+ should "parse to single terminal" do
115
+ path = '$[2:4:2]'
116
+ assert_resolves(@shallow, path, [3, 5])
117
+ end
118
+ should "parse to multiple terminals" do
119
+ path = '$[2:4:2].*.name'
120
+ assert_resolves(@deep, path, %w(e1 f1 i1 j1))
121
+ end
122
+ end
123
+ end
124
+ context "with explicit start and implict stop" do
125
+ context "with implicit step" do
126
+ should "parse to single terminal" do
127
+ path = '$[2:]'
128
+ assert_resolves(@shallow, path, [3, 4, 5, 6])
129
+ end
130
+ should "parse to multiple terminals" do
131
+ path = '$[2:].*.name'
132
+ assert_resolves(@deep, path, %w(e1 f1 g1 h1 i1 j1 k1 l1 m1 n1))
133
+ end
134
+ end
135
+ context "with explicit step" do
136
+ should "parse to single terminal" do
137
+ path = '$[2::2]'
138
+ assert_resolves(@shallow, path, [3, 5])
139
+ end
140
+ should "parse to multiple terminals" do
141
+ path = '$[2::2].*.name'
142
+ assert_resolves(@deep, path, %w(e1 f1 i1 j1 m1 n1))
143
+ end
144
+ end
145
+ end
146
+ end
147
+ context "supporting filters in Ruby" do
148
+ setup do
149
+ @numbers = [1, 2, 3, 4, 5, 6, 7, 8]
150
+ @hashes = [
151
+ {"name" => 'Bruce', "age" => 29},
152
+ {"name" => "Braedyn", "age" => 3},
153
+ {"name" => "Jamis", "age" => 2},
154
+ ]
155
+ end
156
+ context "when using self-contained single statements" do
157
+ should "support simple object operations" do
158
+ path = '$[?(@ % 2 == 0)]'
159
+ assert_resolves(@numbers, path, [2, 4, 6, 8])
160
+ end
161
+ should "support manual object pathing" do
162
+ path = %($[?(@['age'] % 2 == 0)].name)
163
+ assert_resolves(@hashes, path, ["Jamis"])
164
+ end
165
+ end
166
+
167
+ end
168
+ context "descendants" do
169
+ setup do
170
+ @object = {
171
+ "a" => [1, 2, [3, 4]],
172
+ "b" => {
173
+ "c" => 5,
174
+ "e" => [6, 7]
175
+ }
176
+ }
177
+ end
178
+ should "be found with wildcard" do
179
+ path = '$..*'
180
+ assert_resolves(@object, path, [[1, 2, [3, 4]], 1, 2, [3, 4], 3, 4, {"c" => 5, "e" => [6, 7]}, 5, [6, 7], 6, 7, @object])
181
+ end
182
+ should "be found with deeper key" do
183
+ path = '$..e'
184
+ assert_resolves(@object, path, [[6, 7]])
185
+ end
186
+ should "be found with deeper index" do
187
+ path = '$..[0]'
188
+ assert_resolves(@object, path, [1, 3, 6])
189
+ end
190
+ should "resolve deeper chained selectors" do
191
+ path = '$..e[?(@ % 2 == 0)]'
192
+ assert_resolves(@object, path, [6])
193
+ end
194
+
195
+ end
196
+
197
+ end
198
+
199
+ end
@@ -0,0 +1,136 @@
1
+ require File.dirname(__FILE__) << "/test_helper"
2
+ require 'json'
3
+
4
+ # The content below was taken from the tests for the JS and PHP
5
+ # reference implementations at http://code.google.com/p/jsonpath/
6
+ class ReferenceTest < Test::Unit::TestCase
7
+
8
+ context 'Sample 1' do
9
+ setup { @json = %({"a":"a","b":"b","c d":"e"}) }
10
+ should 'resolve a simple path' do
11
+ assert_resolves(object, "$.a", ["a"])
12
+ end
13
+ should 'resolve a path with quotes in brackets' do
14
+ assert_resolves(object, "$['a']", ["a"])
15
+ end
16
+ should 'resolve a path with a space' do
17
+ assert_resolves(object, "$.'c d'", ["e"])
18
+ end
19
+ should 'resolve a star' do
20
+ assert_resolves(object, "$.*", ["a", "b", "e"])
21
+ end
22
+ should 'resolve a star with quotes in brackets' do
23
+ assert_resolves(object, "$['*']", ["a", "b", "e"])
24
+ end
25
+ should 'resolve a star with quotes' do
26
+ assert_resolves(object, "$[*]", ["a", "b", "e"])
27
+ end
28
+ end
29
+ context 'Sample 2' do
30
+ setup { @json = %([1, "2", 3.14, true, null]) }
31
+ should 'resolve with a number in brackets' do
32
+ assert_resolves(object, "$[0]", [1])
33
+ end
34
+ should 'resolve another number in brackets' do
35
+ assert_resolves(object, "$[4]", [nil])
36
+ end
37
+ should 'resolve a star in brackets' do
38
+ assert_resolves(object, "$[*]", [1, "2", 3.14, true, nil])
39
+ end
40
+ should 'resolve an end slice' do
41
+ assert_resolves(object, "$[-1:]", [nil])
42
+ end
43
+ end
44
+ context 'Sample 3' do
45
+ setup { @json = %({"points":[{"id": "i1", "x": 4, "y": -5}, {"id": "i2", "x": -2, "y": 2, "z": 1}, {"id": "i3", "x": 8, "y": 3}, {"id": "i4", "x": -6, "y": -1}, {"id": "i5", "x": 0, "y": 2, "z": 1}, {"id": "i6", "x": 1, "y": 4}]}) }
46
+ should 'resolve correctly' do
47
+ assert_resolves(object, "$.points[1]", [{"id" => "i2", "x" => -2, "y" => 2, "z" => 1}])
48
+ end
49
+ should 'resolve a chained path' do
50
+ assert_resolves(object, "$.points[4].x", [0])
51
+ end
52
+ should 'resolve by attribute match' do
53
+ assert_resolves(object, "$.points[?(@['id']=='i4')].x", [-6])
54
+ end
55
+ should 'resolve a chained path with a star in brackets' do
56
+ assert_resolves(object, "$.points[*].x", [4, -2, 8, -6, 0, 1])
57
+ end
58
+ should 'resolve by attribute operation' do
59
+ assert_resolves(object, "$['points'][?(@['x']*@['x']+@['y']*@['y'] > 50)].id", ["i3"])
60
+ end
61
+ should 'resolve by attribute existence' do
62
+ assert_resolves(object, "$.points[?(@['z'])].id", ["i2", "i5"])
63
+ end
64
+ should 'resolve by length property operation' do
65
+ assert_resolves(object, "$.points[(@.length-1)].id", ["i6"])
66
+ end
67
+ end
68
+ context 'Sample 4' do
69
+ setup { @json = %({"menu":{"header":"SVG Viewer","items":[{"id": "Open"}, {"id": "OpenNew", "label": "Open New"}, null, {"id": "ZoomIn", "label": "Zoom In"}, {"id": "ZoomOut", "label": "Zoom Out"}, {"id": "OriginalView", "label": "Original View"}, null, {"id": "Quality"}, {"id": "Pause"}, {"id": "Mute"}, null, {"id": "Find", "label": "Find..."}, {"id": "FindAgain", "label": "Find Again"}, {"id": "Copy"}, {"id": "CopyAgain", "label": "Copy Again"}, {"id": "CopySVG", "label": "Copy SVG"}, {"id": "ViewSVG", "label": "View SVG"}, {"id": "ViewSource", "label": "View Source"}, {"id": "SaveAs", "label": "Save As"}, null, {"id": "Help"}, {"id": "About", "label": "About Adobe CVG Viewer..."}]}}) }
70
+ should 'resolve testing on attribute' do
71
+ assert_resolves(object, "$.menu.items[?(@ && @['id'] && !@['label'])].id", ["Open", "Quality", "Pause", "Mute", "Copy", "Help"])
72
+ end
73
+ should 'resolve testing on attribute with regular expression' do
74
+ assert_resolves(object, "$.menu.items[?(@ && @['label'] && @['label'] =~ /SVG/)].id", ["CopySVG", "ViewSVG"])
75
+ end
76
+ should 'resolve on negative' do
77
+ # !nil == true in Ruby
78
+ assert_resolves(object, "$.menu.items[?(!@)]", [nil, nil, nil, nil])
79
+ end
80
+ should 'resolve descendant with number in brackets' do
81
+ assert_resolves(object, "$..[0]", [{"id" => "Open"}])
82
+ end
83
+ end
84
+ context 'Sample 5' do
85
+ setup { @json = %({"a":[1, 2, 3, 4],"b":[5, 6, 7, 8]}) }
86
+ should 'resolve descendant with number in brackets' do
87
+ assert_resolves(object, "$..[0]", [1, 5])
88
+ end
89
+ should 'resolve descendant last items' do
90
+ assert_resolves(object, "$..[-1:]", [4, 8])
91
+ end
92
+ should 'resolve by descendant value' do
93
+ assert_resolves(object, "$..[?(@.is_a?(Numeric) && @ % 2 == 0)]", [2, 4, 6, 8])
94
+ end
95
+ end
96
+ context 'Sample 6' do
97
+ setup { @json = %({"lin":{"color":"red","x":2,"y":3},"cir":{"color":"blue","x":5,"y":2,"r":1},"arc":{"color":"green","x":2,"y":4,"r":2,"phi0":30,"dphi":120},"pnt":{"x":0,"y":7}}) }
98
+ should 'resolve by operation in quotes' do
99
+ assert_resolves(object, "$.'?(@['color'])'.x", [2, 5, 2])
100
+ end
101
+ should 'resolve by multiple quoted values in brackets' do
102
+ assert_resolves(object, "$['lin','cir'].color", ["red", "blue"])
103
+ end
104
+ end
105
+ context 'Sample 7' do
106
+ setup { @json = %({"text":["hello", "world2.0"]}) }
107
+ should 'resolve correctly filter 1' do
108
+ assert_resolves(object, "$.text[?(@.length > 5)]", ["world2.0"])
109
+ end
110
+ should 'resolve correctly filter 2' do
111
+ assert_resolves(object, "$.text[?(@[0, 1] == 'h')]", ["hello"])
112
+ end
113
+ end
114
+ context 'Sample 8' do
115
+ setup { @json = %({"a":{"a":2,"b":3},"b":{"a":4,"b":5},"c":{"a":{"a":6,"b":7},"c":8}}) }
116
+ should 'resolve descendant' do
117
+ assert_resolves(object, "$..a", [{"a" => 2, "b" => 3}, 2, 4, {"a" => 6, "b" => 7}, 6])
118
+ end
119
+ end
120
+ context 'Sample 10' do
121
+ setup { @json = %({"a":[{"a": 5, "@": 2, "$": 3}, {"a": 6, "@": 3, "$": 4}, {"a": 7, "@": 4, "$": 5}]}) }
122
+ should 'resolve with quoted operation and escaped special character' do
123
+ assert_resolves(object, "$.a[?(@['\\@']==3)]", [{"a" => 6, "@" => 3, "$" => 4}])
124
+ end
125
+ should 'resolve with quotes and brackets in operation' do
126
+ assert_resolves(object, "$.a[?(@['$']==5)]", [{"a" => 7, "@" => 4, "$" => 5}])
127
+ end
128
+ end
129
+
130
+ private
131
+
132
+ def object
133
+ JSON.parse(@json)
134
+ end
135
+
136
+ end
@@ -0,0 +1,37 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'jsonpath'
8
+
9
+ class Test::Unit::TestCase
10
+
11
+ private
12
+
13
+ def parser
14
+ @parser ||= JSONPath::Parser.new
15
+ end
16
+
17
+ def parse(path)
18
+ parser.parse(path)
19
+ end
20
+
21
+ def assert_parses(path)
22
+ result = parse(path)
23
+ assert result, parser.failure_reason
24
+ end
25
+
26
+ def assert_resolves(obj, path, result)
27
+ assert_parses path
28
+ assert_equal safe_sort(result), safe_sort(parse(path).walk(obj))
29
+ end
30
+
31
+ def safe_sort(objs)
32
+ objs.sort_by do |obj|
33
+ obj ? obj.to_s : 0.to_s
34
+ end
35
+ end
36
+
37
+ end