jsonpath 1.0.7 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +31 -0
- data/.gitignore +2 -0
- data/Gemfile +1 -1
- data/README.md +133 -37
- data/jsonpath.gemspec +1 -0
- data/lib/jsonpath/dig.rb +57 -0
- data/lib/jsonpath/enumerable.rb +9 -19
- data/lib/jsonpath/parser.rb +10 -5
- data/lib/jsonpath/version.rb +1 -1
- data/lib/jsonpath.rb +46 -4
- data/test/test_jsonpath.rb +272 -11
- data/test/test_readme.rb +117 -0
- metadata +23 -7
- data/.travis.yml +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ab3e08744fe7f8ba3b64670fee8f75a0fa4c9b339de8257f64cdc7ea24f26965
|
4
|
+
data.tar.gz: 447ce47a7c5df36c2fa290da5aafc77304d5e8db9318cfcac2b97dd9ae8c9c8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b458262f098ceea595a91a981136141d0ed3f7ef9782d8a205f619be1bb60a7234af11229527ed9fc9ef3baafec323ad33850d87b4dc389ac482c14bb4aa05f
|
7
|
+
data.tar.gz: d1756aa04d5e07de855439aa0a4895078ab5c3c2c90c4c9ec755ba1da2ffb3fe11e59d1819e03f6a82e8670fd810b1c895da150a9d24176ddf4a86a54705fcbf
|
@@ -0,0 +1,31 @@
|
|
1
|
+
name: test
|
2
|
+
on:
|
3
|
+
- push
|
4
|
+
- pull_request
|
5
|
+
jobs:
|
6
|
+
test:
|
7
|
+
strategy:
|
8
|
+
fail-fast: false
|
9
|
+
matrix:
|
10
|
+
ruby-version:
|
11
|
+
- '2.5'
|
12
|
+
- '2.6'
|
13
|
+
- '2.7'
|
14
|
+
- ruby-head
|
15
|
+
- jruby-head
|
16
|
+
- truffleruby-head
|
17
|
+
runs-on:
|
18
|
+
- ubuntu-latest
|
19
|
+
|
20
|
+
runs-on: ${{ matrix.runs-on }}
|
21
|
+
|
22
|
+
steps:
|
23
|
+
|
24
|
+
- uses: actions/checkout@v2
|
25
|
+
|
26
|
+
- uses: ruby/setup-ruby@v1
|
27
|
+
with:
|
28
|
+
ruby-version: ${{ matrix.ruby-version }}
|
29
|
+
bundler-cache: true
|
30
|
+
|
31
|
+
- run: bundle exec rake test
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -4,8 +4,8 @@ This is an implementation of http://goessner.net/articles/JsonPath/.
|
|
4
4
|
|
5
5
|
## What is JsonPath?
|
6
6
|
|
7
|
-
JsonPath is a way of addressing elements within a JSON object. Similar to xpath
|
8
|
-
traverse a json object and manipulate or access it.
|
7
|
+
JsonPath is a way of addressing elements within a JSON object. Similar to xpath
|
8
|
+
of yore, JsonPath lets you traverse a json object and manipulate or access it.
|
9
9
|
|
10
10
|
## Usage
|
11
11
|
|
@@ -15,8 +15,8 @@ There is stand-alone usage through the binary `jsonpath`
|
|
15
15
|
|
16
16
|
jsonpath [expression] (file|string)
|
17
17
|
|
18
|
-
If you omit the second argument, it will read stdin, assuming one valid JSON
|
19
|
-
per line. Expression must be a valid jsonpath expression.
|
18
|
+
If you omit the second argument, it will read stdin, assuming one valid JSON
|
19
|
+
object per line. Expression must be a valid jsonpath expression.
|
20
20
|
|
21
21
|
### Library
|
22
22
|
|
@@ -40,8 +40,8 @@ json = <<-HERE_DOC
|
|
40
40
|
HERE_DOC
|
41
41
|
```
|
42
42
|
|
43
|
-
Now that we have a JSON object, let's get all the prices present in the object.
|
44
|
-
in the following way.
|
43
|
+
Now that we have a JSON object, let's get all the prices present in the object.
|
44
|
+
We create an object for the path in the following way.
|
45
45
|
|
46
46
|
```ruby
|
47
47
|
path = JsonPath.new('$..price')
|
@@ -54,14 +54,15 @@ path.on(json)
|
|
54
54
|
# => [19.95, 8.95, 12.99, 8.99, 22.99]
|
55
55
|
```
|
56
56
|
|
57
|
-
Or on some other object ...
|
57
|
+
Or reuse it later on some other object (thread safe) ...
|
58
58
|
|
59
59
|
```ruby
|
60
60
|
path.on('{"books":[{"title":"A Tale of Two Somethings","price":18.88}]}')
|
61
61
|
# => [18.88]
|
62
62
|
```
|
63
63
|
|
64
|
-
You can also just combine this into one mega-call with the convenient
|
64
|
+
You can also just combine this into one mega-call with the convenient
|
65
|
+
`JsonPath.on` method.
|
65
66
|
|
66
67
|
```ruby
|
67
68
|
JsonPath.on(json, '$..author')
|
@@ -73,29 +74,36 @@ Of course the full JsonPath syntax is supported, such as array slices
|
|
73
74
|
```ruby
|
74
75
|
JsonPath.new('$..book[::2]').on(json)
|
75
76
|
# => [
|
76
|
-
# {"price"=>8.95, "category"=>"reference", "
|
77
|
-
# {"price"=>8.99, "category"=>"fiction", "
|
77
|
+
# {"price" => 8.95, "category" => "reference", "title" => "Sayings of the Century", "author" => "Nigel Rees"},
|
78
|
+
# {"price" => 8.99, "category" => "fiction", "isbn" => "0-553-21311-3", "title" => "Moby Dick", "author" => "Herman Melville","color" => "blue"},
|
78
79
|
# ]
|
79
80
|
```
|
80
81
|
|
81
|
-
...and evals
|
82
|
+
...and evals, including those with conditional operators
|
82
83
|
|
83
84
|
```ruby
|
84
|
-
JsonPath.new(
|
85
|
+
JsonPath.new("$..price[?(@ < 10)]").on(json)
|
85
86
|
# => [8.95, 8.99]
|
87
|
+
|
88
|
+
JsonPath.new("$..book[?(@['price'] == 8.95 || @['price'] == 8.99)].title").on(json)
|
89
|
+
# => ["Sayings of the Century", "Moby Dick"]
|
90
|
+
|
91
|
+
JsonPath.new("$..book[?(@['price'] == 8.95 && @['price'] == 8.99)].title").on(json)
|
92
|
+
# => []
|
86
93
|
```
|
87
94
|
|
88
|
-
There is a convenience method, `#first` that gives you the first element for a
|
95
|
+
There is a convenience method, `#first` that gives you the first element for a
|
96
|
+
JSON object and path.
|
89
97
|
|
90
98
|
```ruby
|
91
|
-
JsonPath.new('$..color').first(
|
99
|
+
JsonPath.new('$..color').first(json)
|
92
100
|
# => "red"
|
93
101
|
```
|
94
102
|
|
95
103
|
As well, we can directly create an `Enumerable` at any time using `#[]`.
|
96
104
|
|
97
105
|
```ruby
|
98
|
-
enum = JsonPath.new('$..color')[
|
106
|
+
enum = JsonPath.new('$..color')[json]
|
99
107
|
# => #<JsonPath::Enumerable:...>
|
100
108
|
enum.first
|
101
109
|
# => "red"
|
@@ -103,29 +111,77 @@ enum.any?{ |c| c == 'red' }
|
|
103
111
|
# => true
|
104
112
|
```
|
105
113
|
|
106
|
-
|
114
|
+
For more usage examples and variations on paths, please visit the tests. There
|
115
|
+
are some more complex ones as well.
|
116
|
+
|
117
|
+
### Querying ruby data structures
|
118
|
+
|
119
|
+
If you have ruby hashes with symbolized keys as input, you
|
120
|
+
can use `:use_symbols` to make JsonPath work fine on them too:
|
107
121
|
|
108
|
-
|
122
|
+
```ruby
|
123
|
+
book = { title: "Sayings of the Century" }
|
124
|
+
|
125
|
+
JsonPath.new('$.title').on(book)
|
126
|
+
# => []
|
127
|
+
|
128
|
+
JsonPath.new('$.title', use_symbols: true).on(book)
|
129
|
+
# => ["Sayings of the Century"]
|
130
|
+
```
|
109
131
|
|
110
|
-
|
132
|
+
JsonPath also recognizes objects responding to `dig` (introduced
|
133
|
+
in ruby 2.3), and therefore works out of the box with Struct,
|
134
|
+
OpenStruct, and other Hash-like structures:
|
111
135
|
|
112
136
|
```ruby
|
113
|
-
|
114
|
-
|
115
|
-
end
|
137
|
+
book_class = Struct.new(:title)
|
138
|
+
book = book_class.new("Sayings of the Century")
|
116
139
|
|
117
|
-
|
118
|
-
|
119
|
-
|
140
|
+
JsonPath.new('$.title').on(book)
|
141
|
+
# => ["Sayings of the Century"]
|
142
|
+
```
|
143
|
+
|
144
|
+
JsonPath is able to query pure ruby objects and uses `__send__`
|
145
|
+
on them. The option is enabled by default in JsonPath 1.x, but
|
146
|
+
we encourage to enable it explicitly:
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
book_class = Class.new{ attr_accessor :title }
|
150
|
+
book = book_class.new
|
151
|
+
book.title = "Sayings of the Century"
|
120
152
|
|
121
|
-
|
122
|
-
|
123
|
-
|
153
|
+
JsonPath.new('$.title', allow_send: true).on(book)
|
154
|
+
# => ["Sayings of the Century"]
|
155
|
+
```
|
156
|
+
|
157
|
+
### Other available options
|
158
|
+
|
159
|
+
By default, JsonPath does not return null values on unexisting paths.
|
160
|
+
This can be changed using the `:default_path_leaf_to_null` option
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
JsonPath.new('$..book[*].isbn').on(json)
|
164
|
+
# => ["0-553-21311-3", "0-395-19395-8"]
|
165
|
+
|
166
|
+
JsonPath.new('$..book[*].isbn', default_path_leaf_to_null: true).on(json)
|
167
|
+
# => [nil, nil, "0-553-21311-3", "0-395-19395-8"]
|
168
|
+
```
|
169
|
+
|
170
|
+
When JsonPath returns a Hash, you can ask to symbolize its keys
|
171
|
+
using the `:symbolize_keys` option
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
JsonPath.new('$..book[0]').on(json)
|
175
|
+
# => [{"category" => "reference", ...}]
|
176
|
+
|
177
|
+
JsonPath.new('$..book[0]', symbolize_keys: true).on(json)
|
178
|
+
# => [{category: "reference", ...}]
|
124
179
|
```
|
125
180
|
|
126
181
|
### Selecting Values
|
127
182
|
|
128
|
-
It's possible to select results once a query has been defined after the query. For
|
183
|
+
It's possible to select results once a query has been defined after the query. For
|
184
|
+
example given this JSON data:
|
129
185
|
|
130
186
|
```bash
|
131
187
|
{
|
@@ -168,15 +224,10 @@ It's possible to select results once a query has been defined after the query. F
|
|
168
224
|
]
|
169
225
|
```
|
170
226
|
|
171
|
-
### Running an individual test
|
172
|
-
|
173
|
-
```ruby
|
174
|
-
ruby -Ilib:../lib test/test_jsonpath.rb --name test_wildcard_on_intermediary_element_v6
|
175
|
-
```
|
176
|
-
|
177
227
|
### Manipulation
|
178
228
|
|
179
|
-
If you'd like to do substitution in a json object, you can use `#gsub`
|
229
|
+
If you'd like to do substitution in a json object, you can use `#gsub`
|
230
|
+
or `#gsub!` to modify the object in place.
|
180
231
|
|
181
232
|
```ruby
|
182
233
|
JsonPath.for('{"candy":"lollipop"}').gsub('$..candy') {|v| "big turks" }.to_hash
|
@@ -188,7 +239,9 @@ The result will be
|
|
188
239
|
{'candy' => 'big turks'}
|
189
240
|
```
|
190
241
|
|
191
|
-
If you'd like to remove all nil keys, you can use `#compact` and `#compact!`.
|
242
|
+
If you'd like to remove all nil keys, you can use `#compact` and `#compact!`.
|
243
|
+
To remove all keys under a certain path, use `#delete` or `#delete!`. You can
|
244
|
+
even chain these methods together as follows:
|
192
245
|
|
193
246
|
```ruby
|
194
247
|
json = '{"candy":"lollipop","noncandy":null,"other":"things"}'
|
@@ -200,6 +253,49 @@ o = JsonPath.for(json).
|
|
200
253
|
# => {"candy" => "big turks"}
|
201
254
|
```
|
202
255
|
|
256
|
+
### Fetch all paths
|
257
|
+
|
258
|
+
To fetch all possible paths in given json, you can use `fetch_all_paths` method.
|
259
|
+
|
260
|
+
data:
|
261
|
+
|
262
|
+
```bash
|
263
|
+
{
|
264
|
+
"store": {
|
265
|
+
"book": [
|
266
|
+
{
|
267
|
+
"category": "reference",
|
268
|
+
"author": "Nigel Rees"
|
269
|
+
},
|
270
|
+
{
|
271
|
+
"category": "fiction",
|
272
|
+
"author": "Evelyn Waugh"
|
273
|
+
}
|
274
|
+
]
|
275
|
+
}
|
276
|
+
```
|
277
|
+
|
278
|
+
... and this query:
|
279
|
+
|
280
|
+
```ruby
|
281
|
+
JsonPath.fetch_all_path(data)
|
282
|
+
```
|
283
|
+
|
284
|
+
... the result will be:
|
285
|
+
|
286
|
+
```bash
|
287
|
+
["$", "$.store", "$.store.book", "$.store.book[0].category", "$.store.book[0].author", "$.store.book[0]", "$.store.book[1].category", "$.store.book[1].author", "$.store.book[1]"]
|
288
|
+
```
|
289
|
+
|
290
|
+
|
291
|
+
|
203
292
|
# Contributions
|
204
293
|
|
205
|
-
Please feel free to submit an Issue or a Pull Request any time you feel like
|
294
|
+
Please feel free to submit an Issue or a Pull Request any time you feel like
|
295
|
+
you would like to contribute. Thank you!
|
296
|
+
|
297
|
+
## Running an individual test
|
298
|
+
|
299
|
+
```ruby
|
300
|
+
ruby -Ilib:../lib test/test_jsonpath.rb --name test_wildcard_on_intermediary_element_v6
|
301
|
+
```
|
data/jsonpath.gemspec
CHANGED
data/lib/jsonpath/dig.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class JsonPath
|
4
|
+
module Dig
|
5
|
+
|
6
|
+
# Similar to what Hash#dig or Array#dig
|
7
|
+
def dig(context, *keys)
|
8
|
+
keys.inject(context){|memo,k|
|
9
|
+
dig_one(memo, k)
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns a hash mapping each key from keys
|
14
|
+
# to its dig value on context.
|
15
|
+
def dig_as_hash(context, keys)
|
16
|
+
keys.each_with_object({}) do |k, memo|
|
17
|
+
memo[k] = dig_one(context, k)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Dig the value of k on context.
|
22
|
+
def dig_one(context, k)
|
23
|
+
case context
|
24
|
+
when Hash
|
25
|
+
context[@options[:use_symbols] ? k.to_sym : k]
|
26
|
+
when Array
|
27
|
+
context[k.to_i]
|
28
|
+
else
|
29
|
+
if context.respond_to?(:dig)
|
30
|
+
context.dig(k)
|
31
|
+
elsif @options[:allow_send]
|
32
|
+
context.__send__(k)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Yields the block if context has a diggable
|
38
|
+
# value for k
|
39
|
+
def yield_if_diggable(context, k, &blk)
|
40
|
+
case context
|
41
|
+
when Array
|
42
|
+
nil
|
43
|
+
when Hash
|
44
|
+
k = @options[:use_symbols] ? k.to_sym : k
|
45
|
+
return yield if context.key?(k) || @options[:default_path_leaf_to_null]
|
46
|
+
else
|
47
|
+
if context.respond_to?(:dig)
|
48
|
+
digged = dig_one(context, k)
|
49
|
+
yield if !digged.nil? || @options[:default_path_leaf_to_null]
|
50
|
+
elsif @options[:allow_send] && context.respond_to?(k.to_s) && !Object.respond_to?(k.to_s)
|
51
|
+
yield
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
data/lib/jsonpath/enumerable.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
class JsonPath
|
4
4
|
class Enumerable
|
5
5
|
include ::Enumerable
|
6
|
+
include Dig
|
6
7
|
|
7
8
|
def initialize(path, object, mode, options = {})
|
8
9
|
@path = path.path
|
@@ -12,12 +13,7 @@ class JsonPath
|
|
12
13
|
end
|
13
14
|
|
14
15
|
def each(context = @object, key = nil, pos = 0, &blk)
|
15
|
-
node =
|
16
|
-
if key
|
17
|
-
context.is_a?(Hash) || context.is_a?(Array) ? context[key] : context.__send__(key)
|
18
|
-
else
|
19
|
-
context
|
20
|
-
end
|
16
|
+
node = key ? dig_one(context, key) : context
|
21
17
|
@_current_node = node
|
22
18
|
return yield_value(blk, context, key) if pos == @path.size
|
23
19
|
|
@@ -47,11 +43,10 @@ class JsonPath
|
|
47
43
|
def filter_context(context, keys)
|
48
44
|
case context
|
49
45
|
when Hash
|
50
|
-
|
51
|
-
context.select { |k| keys.include?(k) }
|
46
|
+
dig_as_hash(context, keys)
|
52
47
|
when Array
|
53
48
|
context.each_with_object([]) do |c, memo|
|
54
|
-
memo << c
|
49
|
+
memo << dig_as_hash(c, keys)
|
55
50
|
end
|
56
51
|
end
|
57
52
|
end
|
@@ -61,16 +56,14 @@ class JsonPath
|
|
61
56
|
case sub_path[0]
|
62
57
|
when '\'', '"'
|
63
58
|
k = sub_path[1, sub_path.size - 2]
|
64
|
-
|
65
|
-
node[k] ||= nil if @options[:default_path_leaf_to_null]
|
66
|
-
each(node, k, pos + 1, &blk) if node.key?(k)
|
67
|
-
elsif node.respond_to?(k.to_s) && !Object.respond_to?(k.to_s)
|
59
|
+
yield_if_diggable(node, k) do
|
68
60
|
each(node, k, pos + 1, &blk)
|
69
61
|
end
|
70
62
|
when '?'
|
71
63
|
handle_question_mark(sub_path, node, pos, &blk)
|
72
64
|
else
|
73
65
|
next if node.is_a?(Array) && node.empty?
|
66
|
+
next if node.nil? # when default_path_leaf_to_null is true
|
74
67
|
|
75
68
|
array_args = sub_path.split(':')
|
76
69
|
if array_args[0] == '*'
|
@@ -130,7 +123,7 @@ class JsonPath
|
|
130
123
|
def yield_value(blk, context, key)
|
131
124
|
case @mode
|
132
125
|
when nil
|
133
|
-
blk.call(key ? context
|
126
|
+
blk.call(key ? dig_one(context, key) : context)
|
134
127
|
when :compact
|
135
128
|
if key && context[key].nil?
|
136
129
|
key.is_a?(Integer) ? context.delete_at(key) : context.delete(key)
|
@@ -158,16 +151,13 @@ class JsonPath
|
|
158
151
|
identifiers = /@?((?<!\d)\.(?!\d)(\w+))+/.match(exp)
|
159
152
|
if !identifiers.nil? && !@_current_node.methods.include?(identifiers[2].to_sym)
|
160
153
|
exp_to_eval = exp.dup
|
161
|
-
exp_to_eval[identifiers[0]] = identifiers[0].split('.').map do |el|
|
162
|
-
el == '@' ? '@' : "['#{el}']"
|
163
|
-
end.join
|
164
154
|
begin
|
165
|
-
return JsonPath::Parser.new(@_current_node).parse(exp_to_eval)
|
155
|
+
return JsonPath::Parser.new(@_current_node, @options).parse(exp_to_eval)
|
166
156
|
rescue StandardError
|
167
157
|
return default
|
168
158
|
end
|
169
159
|
end
|
170
|
-
JsonPath::Parser.new(@_current_node).parse(exp)
|
160
|
+
JsonPath::Parser.new(@_current_node, @options).parse(exp)
|
171
161
|
end
|
172
162
|
end
|
173
163
|
end
|
data/lib/jsonpath/parser.rb
CHANGED
@@ -5,11 +5,14 @@ require 'strscan'
|
|
5
5
|
class JsonPath
|
6
6
|
# Parser parses and evaluates an expression passed to @_current_node.
|
7
7
|
class Parser
|
8
|
+
include Dig
|
9
|
+
|
8
10
|
REGEX = /\A\/(.+)\/([imxnesu]*)\z|\A%r{(.+)}([imxnesu]*)\z/
|
9
11
|
|
10
|
-
def initialize(node)
|
12
|
+
def initialize(node, options)
|
11
13
|
@_current_node = node
|
12
14
|
@_expr_map = {}
|
15
|
+
@options = options
|
13
16
|
end
|
14
17
|
|
15
18
|
# parse will parse an expression in the following way.
|
@@ -65,7 +68,7 @@ class JsonPath
|
|
65
68
|
scanner = StringScanner.new(exp)
|
66
69
|
elements = []
|
67
70
|
until scanner.eos?
|
68
|
-
if (t = scanner.scan(/\['[a-zA-Z@&*\/$%^?_]+'\]|\.[a-zA-Z0-9_]+[
|
71
|
+
if (t = scanner.scan(/\['[a-zA-Z@&*\/$%^?_]+'\]|\.[a-zA-Z0-9_]+[?]?/))
|
69
72
|
elements << t.gsub(/[\[\]'.]|\s+/, '')
|
70
73
|
elsif (t = scanner.scan(/(\s+)?[<>=!\-+][=~]?(\s+)?/))
|
71
74
|
operator = t
|
@@ -91,7 +94,7 @@ class JsonPath
|
|
91
94
|
el = if elements.empty?
|
92
95
|
@_current_node
|
93
96
|
elsif @_current_node.is_a?(Hash)
|
94
|
-
@_current_node
|
97
|
+
dig(@_current_node, *elements)
|
95
98
|
else
|
96
99
|
elements.inject(@_current_node, &:__send__)
|
97
100
|
end
|
@@ -156,9 +159,11 @@ class JsonPath
|
|
156
159
|
res = bool_or_exp(top.shift)
|
157
160
|
top.each_with_index do |item, index|
|
158
161
|
if item == '&&'
|
159
|
-
|
162
|
+
next_value = bool_or_exp(top[index + 1])
|
163
|
+
res &&= next_value
|
160
164
|
elsif item == '||'
|
161
|
-
|
165
|
+
next_value = bool_or_exp(top[index + 1])
|
166
|
+
res ||= next_value
|
162
167
|
end
|
163
168
|
end
|
164
169
|
|
data/lib/jsonpath/version.rb
CHANGED
data/lib/jsonpath.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'strscan'
|
4
4
|
require 'multi_json'
|
5
5
|
require 'jsonpath/proxy'
|
6
|
+
require 'jsonpath/dig'
|
6
7
|
require 'jsonpath/enumerable'
|
7
8
|
require 'jsonpath/version'
|
8
9
|
require 'jsonpath/parser'
|
@@ -12,10 +13,18 @@ require 'jsonpath/parser'
|
|
12
13
|
class JsonPath
|
13
14
|
PATH_ALL = '$..*'
|
14
15
|
|
16
|
+
DEFAULT_OPTIONS = {
|
17
|
+
:default_path_leaf_to_null => false,
|
18
|
+
:symbolize_keys => false,
|
19
|
+
:use_symbols => false,
|
20
|
+
:allow_send => true,
|
21
|
+
:max_nesting => 100
|
22
|
+
}
|
23
|
+
|
15
24
|
attr_accessor :path
|
16
25
|
|
17
26
|
def initialize(path, opts = {})
|
18
|
-
@opts = opts
|
27
|
+
@opts = DEFAULT_OPTIONS.merge(opts)
|
19
28
|
scanner = StringScanner.new(path.strip)
|
20
29
|
@path = []
|
21
30
|
until scanner.eos?
|
@@ -78,13 +87,46 @@ class JsonPath
|
|
78
87
|
end
|
79
88
|
a
|
80
89
|
end
|
90
|
+
|
91
|
+
def self.fetch_all_path(obj)
|
92
|
+
all_paths = ['$']
|
93
|
+
find_path(obj, '$', all_paths, obj.class == Array)
|
94
|
+
return all_paths
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.find_path(obj, root_key, all_paths, is_array = false)
|
98
|
+
obj.each do |key, value|
|
99
|
+
table_params = { key: key, root_key: root_key}
|
100
|
+
is_loop = value.class == Array || value.class == Hash
|
101
|
+
if is_loop
|
102
|
+
path_exp = construct_path(table_params)
|
103
|
+
all_paths << path_exp
|
104
|
+
find_path(value, path_exp, all_paths, value.class == Array)
|
105
|
+
elsif is_array
|
106
|
+
table_params[:index] = obj.find_index(key)
|
107
|
+
path_exp = construct_path(table_params)
|
108
|
+
find_path(key, path_exp, all_paths, key.class == Array) if key.class == Hash || key.class == Array
|
109
|
+
all_paths << path_exp
|
110
|
+
else
|
111
|
+
all_paths << construct_path(table_params)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.construct_path(table_row)
|
117
|
+
if table_row[:index]
|
118
|
+
return table_row[:root_key] + '['+ table_row[:index].to_s + ']'
|
119
|
+
else
|
120
|
+
return table_row[:root_key] + '.'+ table_row[:key]
|
121
|
+
end
|
122
|
+
end
|
81
123
|
|
82
124
|
def first(obj_or_str, *args)
|
83
125
|
enum_on(obj_or_str).first(*args)
|
84
126
|
end
|
85
127
|
|
86
128
|
def enum_on(obj_or_str, mode = nil)
|
87
|
-
JsonPath::Enumerable.new(self, self.class.process_object(obj_or_str), mode,
|
129
|
+
JsonPath::Enumerable.new(self, self.class.process_object(obj_or_str, @opts), mode,
|
88
130
|
@opts)
|
89
131
|
end
|
90
132
|
alias [] enum_on
|
@@ -99,8 +141,8 @@ class JsonPath
|
|
99
141
|
|
100
142
|
private
|
101
143
|
|
102
|
-
def self.process_object(obj_or_str)
|
103
|
-
obj_or_str.is_a?(String) ? MultiJson.decode(obj_or_str) : obj_or_str
|
144
|
+
def self.process_object(obj_or_str, opts = {})
|
145
|
+
obj_or_str.is_a?(String) ? MultiJson.decode(obj_or_str, max_nesting: opts[:max_nesting]) : obj_or_str
|
104
146
|
end
|
105
147
|
|
106
148
|
def deep_clone
|
data/test/test_jsonpath.rb
CHANGED
@@ -70,24 +70,60 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
70
70
|
assert_equal [@object['store']['book'][0], @object['store']['book'][2]], JsonPath.new("$..book[?(@['price'] < 10)]").on(@object)
|
71
71
|
assert_equal [@object['store']['book'][0], @object['store']['book'][2]], JsonPath.new("$..book[?(@['price'] == 9)]").on(@object)
|
72
72
|
assert_equal [@object['store']['book'][3]], JsonPath.new("$..book[?(@['price'] > 20)]").on(@object)
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_not_equals_operator
|
76
|
+
expected =
|
77
|
+
[
|
78
|
+
@object['store']['book'][0],
|
79
|
+
@object['store']['book'][4],
|
80
|
+
@object['store']['book'][5],
|
81
|
+
@object['store']['book'][6]
|
82
|
+
]
|
83
|
+
assert_equal(expected, JsonPath.new("$..book[?(@['category'] != 'fiction')]").on(@object))
|
84
|
+
assert_equal(expected, JsonPath.new("$..book[?(@['category']!=fiction)]").on(@object))
|
85
|
+
assert_equal(expected, JsonPath.new("$..book[?(@.category!=fiction)]").on(@object))
|
86
|
+
assert_equal(expected, JsonPath.new("$..book[?(@.category != 'fiction')]").on(@object))
|
79
87
|
end
|
80
88
|
|
81
89
|
def test_or_operator
|
82
90
|
assert_equal [@object['store']['book'][1], @object['store']['book'][3]], JsonPath.new("$..book[?(@['price'] == 13 || @['price'] == 23)]").on(@object)
|
91
|
+
result = ["Sayings of the Century", "Sword of Honour", "Moby Dick", "The Lord of the Rings"]
|
92
|
+
assert_equal result, JsonPath.new("$..book[?(@.price==13 || @.price==9 || @.price==23)].title").on(@object)
|
93
|
+
assert_equal result, JsonPath.new("$..book[?(@.price==9 || @.price==23 || @.price==13)].title").on(@object)
|
94
|
+
assert_equal result, JsonPath.new("$..book[?(@.price==23 || @.price==13 || @.price==9)].title").on(@object)
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_or_operator_with_not_equals
|
98
|
+
# Should be the same regardless of key style ( @.key vs @['key'] )
|
99
|
+
result = ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien', 'Lukyanenko']
|
100
|
+
assert_equal result, JsonPath.new("$..book[?(@['title']=='Osennie Vizity' || @['author']!='Lukyanenko')].author").on(@object)
|
101
|
+
assert_equal result, JsonPath.new("$..book[?(@.title=='Osennie Vizity' || @.author != Lukyanenko )].author").on(@object)
|
102
|
+
assert_equal result, JsonPath.new("$..book[?(@.title=='Osennie Vizity' || @.author!=Lukyanenko )].author").on(@object)
|
83
103
|
end
|
84
104
|
|
85
105
|
def test_and_operator
|
86
106
|
assert_equal [], JsonPath.new("$..book[?(@['price'] == 13 && @['price'] == 23)]").on(@object)
|
107
|
+
assert_equal [], JsonPath.new("$..book[?(@.price>=13 && @.category==fiction && @.title==no_match)]").on(@object)
|
108
|
+
assert_equal [], JsonPath.new("$..book[?(@.title==no_match && @.category==fiction && @.price==13)]").on(@object)
|
109
|
+
assert_equal [], JsonPath.new("$..book[?(@.price==13 && @.title==no_match && @.category==fiction)]").on(@object)
|
110
|
+
assert_equal [], JsonPath.new("$..book[?(@.price==13 && @.bad_key_name==true && @.category==fiction)]").on(@object)
|
111
|
+
|
112
|
+
expected = [@object['store']['book'][1]]
|
113
|
+
assert_equal expected, JsonPath.new("$..book[?(@['price'] < 23 && @['price'] > 9)]").on(@object)
|
114
|
+
assert_equal expected, JsonPath.new("$..book[?(@.price < 23 && @.price > 9)]").on(@object)
|
115
|
+
|
116
|
+
expected = ['Sword of Honour', 'The Lord of the Rings']
|
117
|
+
assert_equal expected, JsonPath.new("$..book[?(@.price>=13 && @.category==fiction)].title").on(@object)
|
118
|
+
assert_equal ['The Lord of the Rings'], JsonPath.new("$..book[?(@.category==fiction && @.isbn && @.price>9)].title").on(@object)
|
119
|
+
assert_equal ['Sayings of the Century'], JsonPath.new("$..book[?(@['price'] == 9 && @.author=='Nigel Rees')].title").on(@object)
|
120
|
+
assert_equal ['Sayings of the Century'], JsonPath.new("$..book[?(@['price'] == 9 && @.tags..asdf)].title").on(@object)
|
87
121
|
end
|
88
122
|
|
89
|
-
def
|
90
|
-
|
123
|
+
def test_and_operator_with_not_equals
|
124
|
+
expected = ['Nigel Rees']
|
125
|
+
assert_equal expected, JsonPath.new("$..book[?(@['price']==9 && @['category']!=fiction)].author").on(@object)
|
126
|
+
assert_equal expected, JsonPath.new("$..book[?(@.price==9 && @.category!=fiction)].author").on(@object)
|
91
127
|
end
|
92
128
|
|
93
129
|
def test_nested_grouping
|
@@ -130,6 +166,50 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
130
166
|
assert_equal ['value'], JsonPath.new('$.b').on(object)
|
131
167
|
end
|
132
168
|
|
169
|
+
def test_works_on_object
|
170
|
+
klass = Class.new{
|
171
|
+
attr_reader :b
|
172
|
+
def initialize(b)
|
173
|
+
@b = b
|
174
|
+
end
|
175
|
+
}
|
176
|
+
object = klass.new("value")
|
177
|
+
|
178
|
+
assert_equal ["value"], JsonPath.new('$.b').on(object)
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_works_on_object_can_be_disabled
|
182
|
+
klass = Class.new{
|
183
|
+
attr_reader :b
|
184
|
+
def initialize(b)
|
185
|
+
@b = b
|
186
|
+
end
|
187
|
+
}
|
188
|
+
object = klass.new("value")
|
189
|
+
|
190
|
+
assert_equal [], JsonPath.new('$.b', allow_send: false).on(object)
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_works_on_diggable
|
194
|
+
klass = Class.new{
|
195
|
+
attr_reader :h
|
196
|
+
def initialize(h)
|
197
|
+
@h = h
|
198
|
+
end
|
199
|
+
def dig(*keys)
|
200
|
+
@h.dig(*keys)
|
201
|
+
end
|
202
|
+
}
|
203
|
+
|
204
|
+
object = klass.new('a' => 'some', 'b' => 'value')
|
205
|
+
assert_equal ['value'], JsonPath.new('$.b').on(object)
|
206
|
+
|
207
|
+
object = {
|
208
|
+
"foo" => klass.new('a' => 'some', 'b' => 'value')
|
209
|
+
}
|
210
|
+
assert_equal ['value'], JsonPath.new('$.foo.b').on(object)
|
211
|
+
end
|
212
|
+
|
133
213
|
def test_works_on_non_hash_with_filters
|
134
214
|
klass = Struct.new(:a, :b)
|
135
215
|
first_object = klass.new('some', 'value')
|
@@ -138,6 +218,24 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
138
218
|
assert_equal ['other value'], JsonPath.new('$[?(@.a == "next")].b').on([first_object, second_object])
|
139
219
|
end
|
140
220
|
|
221
|
+
def test_works_on_hash_with_summary
|
222
|
+
object = {
|
223
|
+
"foo" => [{
|
224
|
+
"a" => "some",
|
225
|
+
"b" => "value"
|
226
|
+
}]
|
227
|
+
}
|
228
|
+
assert_equal [{ "b" => "value" }], JsonPath.new("$.foo[*](b)").on(object)
|
229
|
+
end
|
230
|
+
|
231
|
+
def test_works_on_non_hash_with_summary
|
232
|
+
klass = Struct.new(:a, :b)
|
233
|
+
object = {
|
234
|
+
"foo" => [klass.new("some", "value")]
|
235
|
+
}
|
236
|
+
assert_equal [{ "b" => "value" }], JsonPath.new("$.foo[*](b)").on(object)
|
237
|
+
end
|
238
|
+
|
141
239
|
def test_recognize_array_with_evald_index
|
142
240
|
assert_equal [@object['store']['book'][2]], JsonPath.new('$..book[(@.length-5)]').on(@object)
|
143
241
|
end
|
@@ -508,11 +606,42 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
508
606
|
'name' => 'testname2'
|
509
607
|
}]
|
510
608
|
}
|
511
|
-
|
609
|
+
|
610
|
+
# These queries should be equivalent
|
611
|
+
expected = [{ 'isTrue' => true, 'name' => 'testname1' }]
|
612
|
+
assert_equal expected, JsonPath.new('$.data[?(@.isTrue)]').on(data)
|
613
|
+
assert_equal expected, JsonPath.new('$.data[?(@.isTrue==true)]').on(data)
|
614
|
+
assert_equal expected, JsonPath.new('$.data[?(@.isTrue == true)]').on(data)
|
615
|
+
|
616
|
+
# These queries should be equivalent
|
617
|
+
expected = [{ 'isTrue' => false, 'name' => 'testname2' }]
|
618
|
+
assert_equal expected, JsonPath.new('$.data[?(@.isTrue != true)]').on(data)
|
619
|
+
assert_equal expected, JsonPath.new('$.data[?(@.isTrue!=true)]').on(data)
|
620
|
+
assert_equal expected, JsonPath.new('$.data[?(@.isTrue==false)]').on(data)
|
621
|
+
end
|
622
|
+
|
623
|
+
def test_and_operator_with_boolean_parameter_value
|
624
|
+
data = {
|
625
|
+
'data' => [{
|
626
|
+
'hasProperty1' => true,
|
627
|
+
'hasProperty2' => false,
|
628
|
+
'name' => 'testname1'
|
629
|
+
}, {
|
630
|
+
'hasProperty1' => false,
|
631
|
+
'hasProperty2' => true,
|
632
|
+
'name' => 'testname2'
|
633
|
+
}, {
|
634
|
+
'hasProperty1' => true,
|
635
|
+
'hasProperty2' => true,
|
636
|
+
'name' => 'testname3'
|
637
|
+
}]
|
638
|
+
}
|
639
|
+
assert_equal ['testname3'], JsonPath.new('$.data[?(@.hasProperty1 && @.hasProperty2)].name').on(data)
|
512
640
|
end
|
513
641
|
|
514
642
|
def test_regex_simple
|
515
643
|
assert_equal %w[asdf asdf2], JsonPath.new('$.store.book..tags[?(@ =~ /asdf/)]').on(@object)
|
644
|
+
assert_equal %w[asdf asdf2], JsonPath.new('$.store.book..tags[?(@=~/asdf/)]').on(@object)
|
516
645
|
end
|
517
646
|
|
518
647
|
def test_regex_simple_miss
|
@@ -849,7 +978,34 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
849
978
|
assert_equal [], JsonPath.on(json, "$.phoneNumbers[?(@[0].type == 'home')]")
|
850
979
|
end
|
851
980
|
|
852
|
-
def
|
981
|
+
def test_selecting_multiple_keys_on_hash
|
982
|
+
json = '
|
983
|
+
{
|
984
|
+
"category": "reference",
|
985
|
+
"author": "Nigel Rees",
|
986
|
+
"title": "Sayings of the Century",
|
987
|
+
"price": 8.95
|
988
|
+
}
|
989
|
+
'.to_json
|
990
|
+
assert_equal [{ 'category' => 'reference', 'author' => 'Nigel Rees' }], JsonPath.on(json, '$.(category,author)')
|
991
|
+
end
|
992
|
+
|
993
|
+
def test_selecting_multiple_keys_on_sub_hash
|
994
|
+
skip("Failing as the semantics of .(x,y) is unclear")
|
995
|
+
json = '
|
996
|
+
{
|
997
|
+
"book": {
|
998
|
+
"category": "reference",
|
999
|
+
"author": "Nigel Rees",
|
1000
|
+
"title": "Sayings of the Century",
|
1001
|
+
"price": 8.95
|
1002
|
+
}
|
1003
|
+
}
|
1004
|
+
'.to_json
|
1005
|
+
assert_equal [{ 'category' => 'reference', 'author' => 'Nigel Rees' }], JsonPath.on(json, '$.book.(category,author)')
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
def test_selecting_multiple_keys_on_array
|
853
1009
|
json = '
|
854
1010
|
{
|
855
1011
|
"store": {
|
@@ -874,7 +1030,7 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
874
1030
|
assert_equal [{ 'category' => 'reference', 'author' => 'Nigel Rees' }, { 'category' => 'fiction', 'author' => 'Evelyn Waugh' }], JsonPath.on(json, '$.store.book[*](category,author)')
|
875
1031
|
end
|
876
1032
|
|
877
|
-
def
|
1033
|
+
def test_selecting_multiple_keys_on_array_with_filter
|
878
1034
|
json = '
|
879
1035
|
{
|
880
1036
|
"store": {
|
@@ -925,6 +1081,32 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
925
1081
|
assert_equal [{ 'cate gory' => 'reference', 'author' => 'Nigel Rees' }], JsonPath.on(json, "$.store.book[?(@['price'] == 8.95)]( cate gory, author )")
|
926
1082
|
end
|
927
1083
|
|
1084
|
+
def test_use_symbol_opt
|
1085
|
+
json = {
|
1086
|
+
store: {
|
1087
|
+
book: [
|
1088
|
+
{
|
1089
|
+
category: "reference",
|
1090
|
+
author: "Nigel Rees",
|
1091
|
+
title: "Sayings of the Century",
|
1092
|
+
price: 8.95
|
1093
|
+
},
|
1094
|
+
{
|
1095
|
+
category: "fiction",
|
1096
|
+
author: "Evelyn Waugh",
|
1097
|
+
title: "Sword of Honour",
|
1098
|
+
price: 12.99
|
1099
|
+
}
|
1100
|
+
]
|
1101
|
+
}
|
1102
|
+
}
|
1103
|
+
on = ->(path){ JsonPath.on(json, path, use_symbols: true) }
|
1104
|
+
assert_equal ['reference', 'fiction'], on.("$.store.book[*].category")
|
1105
|
+
assert_equal ['reference', 'fiction'], on.("$..category")
|
1106
|
+
assert_equal ['reference'], on.("$.store.book[?(@['price'] == 8.95)].category")
|
1107
|
+
assert_equal [{'category' => 'reference'}], on.("$.store.book[?(@['price'] == 8.95)](category)")
|
1108
|
+
end
|
1109
|
+
|
928
1110
|
def test_object_method_send
|
929
1111
|
j = {height: 5, hash: "some_hash"}.to_json
|
930
1112
|
hs = JsonPath.new "$..send"
|
@@ -945,6 +1127,70 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
945
1127
|
assert_equal ['foo'], JsonPath.new('$.1').on(data.to_json)
|
946
1128
|
end
|
947
1129
|
|
1130
|
+
def test_behavior_on_null_and_missing
|
1131
|
+
data = {
|
1132
|
+
"foo" => nil,
|
1133
|
+
"bar" => {
|
1134
|
+
"baz" => nil
|
1135
|
+
},
|
1136
|
+
"bars" => [
|
1137
|
+
{ "foo" => 12 },
|
1138
|
+
{ "foo" => nil },
|
1139
|
+
{ }
|
1140
|
+
]
|
1141
|
+
}
|
1142
|
+
assert_equal [nil], JsonPath.new('$.foo').on(data)
|
1143
|
+
assert_equal [nil], JsonPath.new('$.bar.baz').on(data)
|
1144
|
+
assert_equal [], JsonPath.new('$.baz').on(data)
|
1145
|
+
assert_equal [], JsonPath.new('$.bar.foo').on(data)
|
1146
|
+
assert_equal [12, nil], JsonPath.new('$.bars[*].foo').on(data)
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
def test_default_path_leaf_to_null_opt
|
1150
|
+
data = {
|
1151
|
+
"foo" => nil,
|
1152
|
+
"bar" => {
|
1153
|
+
"baz" => nil
|
1154
|
+
},
|
1155
|
+
"bars" => [
|
1156
|
+
{ "foo" => 12 },
|
1157
|
+
{ "foo" => nil },
|
1158
|
+
{ }
|
1159
|
+
]
|
1160
|
+
}
|
1161
|
+
assert_equal [nil], JsonPath.new('$.foo', default_path_leaf_to_null: true).on(data)
|
1162
|
+
assert_equal [nil], JsonPath.new('$.bar.baz', default_path_leaf_to_null: true).on(data)
|
1163
|
+
assert_equal [nil], JsonPath.new('$.baz', default_path_leaf_to_null: true).on(data)
|
1164
|
+
assert_equal [nil], JsonPath.new('$.bar.foo', default_path_leaf_to_null: true).on(data)
|
1165
|
+
assert_equal [12, nil, nil], JsonPath.new('$.bars[*].foo', default_path_leaf_to_null: true).on(data)
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
def test_raise_max_nesting_error
|
1169
|
+
json = {
|
1170
|
+
a: {
|
1171
|
+
b: {
|
1172
|
+
c: {
|
1173
|
+
}
|
1174
|
+
}
|
1175
|
+
}
|
1176
|
+
}.to_json
|
1177
|
+
|
1178
|
+
assert_raises(MultiJson::ParseError) { JsonPath.new('$.a', max_nesting: 1).on(json) }
|
1179
|
+
end
|
1180
|
+
|
1181
|
+
def test_with_max_nesting_false
|
1182
|
+
json = {
|
1183
|
+
a: {
|
1184
|
+
b: {
|
1185
|
+
c: {
|
1186
|
+
}
|
1187
|
+
}
|
1188
|
+
}
|
1189
|
+
}.to_json
|
1190
|
+
|
1191
|
+
assert_equal [{}], JsonPath.new('$.a.b.c', max_nesting: false).on(json)
|
1192
|
+
end
|
1193
|
+
|
948
1194
|
def example_object
|
949
1195
|
{ 'store' => {
|
950
1196
|
'book' => [
|
@@ -999,4 +1245,19 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
999
1245
|
'_links' => { 'self' => {} }
|
1000
1246
|
} }
|
1001
1247
|
end
|
1248
|
+
|
1249
|
+
def test_fetch_all_path
|
1250
|
+
data = {
|
1251
|
+
"foo" => nil,
|
1252
|
+
"bar" => {
|
1253
|
+
"baz" => nil
|
1254
|
+
},
|
1255
|
+
"bars" => [
|
1256
|
+
{ "foo" => 12 },
|
1257
|
+
{ "foo" => nil },
|
1258
|
+
{ }
|
1259
|
+
]
|
1260
|
+
}
|
1261
|
+
assert_equal ["$", "$.foo", "$.bar", "$.bar.baz", "$.bars", "$.bars[0].foo", "$.bars[0]", "$.bars[1].foo", "$.bars[1]", "$.bars[2]"], JsonPath.fetch_all_path(data)
|
1262
|
+
end
|
1002
1263
|
end
|
data/test/test_readme.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'minitest/autorun'
|
4
|
+
require 'phocus'
|
5
|
+
require 'jsonpath'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
class TestJsonpathReadme < MiniTest::Unit::TestCase
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@json = <<-HERE_DOC
|
12
|
+
{"store":
|
13
|
+
{"bicycle":
|
14
|
+
{"price":19.95, "color":"red"},
|
15
|
+
"book":[
|
16
|
+
{"price":8.95, "category":"reference", "title":"Sayings of the Century", "author":"Nigel Rees"},
|
17
|
+
{"price":12.99, "category":"fiction", "title":"Sword of Honour", "author":"Evelyn Waugh"},
|
18
|
+
{"price":8.99, "category":"fiction", "isbn":"0-553-21311-3", "title":"Moby Dick", "author":"Herman Melville","color":"blue"},
|
19
|
+
{"price":22.99, "category":"fiction", "isbn":"0-395-19395-8", "title":"The Lord of the Rings", "author":"Tolkien"}
|
20
|
+
]
|
21
|
+
}
|
22
|
+
}
|
23
|
+
HERE_DOC
|
24
|
+
end
|
25
|
+
attr_reader :json
|
26
|
+
|
27
|
+
def test_library_section
|
28
|
+
path = JsonPath.new('$..price')
|
29
|
+
assert_equal [19.95, 8.95, 12.99, 8.99, 22.99], path.on(json)
|
30
|
+
assert_equal [18.88], path.on('{"books":[{"title":"A Tale of Two Somethings","price":18.88}]}')
|
31
|
+
assert_equal ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "Tolkien"], JsonPath.on(json, '$..author')
|
32
|
+
assert_equal [
|
33
|
+
{"price" => 8.95, "category" => "reference", "title" => "Sayings of the Century", "author" => "Nigel Rees"},
|
34
|
+
{"price" => 8.99, "category" => "fiction", "isbn" => "0-553-21311-3", "title" => "Moby Dick", "author" => "Herman Melville","color" => "blue"},
|
35
|
+
], JsonPath.new('$..book[::2]').on(json)
|
36
|
+
assert_equal [8.95, 8.99], JsonPath.new("$..price[?(@ < 10)]").on(json)
|
37
|
+
assert_equal ["Sayings of the Century", "Moby Dick"], JsonPath.new("$..book[?(@['price'] == 8.95 || @['price'] == 8.99)].title").on(json)
|
38
|
+
assert_equal [], JsonPath.new("$..book[?(@['price'] == 8.95 && @['price'] == 8.99)].title").on(json)
|
39
|
+
assert_equal "red", JsonPath.new('$..color').first(json)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_library_section_enumerable
|
43
|
+
enum = JsonPath.new('$..color')[json]
|
44
|
+
assert_equal "red", enum.first
|
45
|
+
assert enum.any?{ |c| c == 'red' }
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_ruby_structures_section
|
49
|
+
book = { title: "Sayings of the Century" }
|
50
|
+
assert_equal [], JsonPath.new('$.title').on(book)
|
51
|
+
assert_equal ["Sayings of the Century"], JsonPath.new('$.title', use_symbols: true).on(book)
|
52
|
+
|
53
|
+
book_class = Struct.new(:title)
|
54
|
+
book = book_class.new("Sayings of the Century")
|
55
|
+
assert_equal ["Sayings of the Century"], JsonPath.new('$.title').on(book)
|
56
|
+
|
57
|
+
book_class = Class.new{ attr_accessor :title }
|
58
|
+
book = book_class.new
|
59
|
+
book.title = "Sayings of the Century"
|
60
|
+
assert_equal ["Sayings of the Century"], JsonPath.new('$.title', allow_send: true).on(book)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_options_section
|
64
|
+
assert_equal ["0-553-21311-3", "0-395-19395-8"], JsonPath.new('$..book[*].isbn').on(json)
|
65
|
+
assert_equal [nil, nil, "0-553-21311-3", "0-395-19395-8"], JsonPath.new('$..book[*].isbn', default_path_leaf_to_null: true).on(json)
|
66
|
+
|
67
|
+
assert_equal ["price", "category", "title", "author"], JsonPath.new('$..book[0]').on(json).map(&:keys).flatten.uniq
|
68
|
+
assert_equal [:price, :category, :title, :author], JsonPath.new('$..book[0]').on(json, symbolize_keys: true).map(&:keys).flatten.uniq
|
69
|
+
end
|
70
|
+
|
71
|
+
def selecting_value_section
|
72
|
+
json = <<-HERE_DOC
|
73
|
+
{
|
74
|
+
"store": {
|
75
|
+
"book": [
|
76
|
+
{
|
77
|
+
"category": "reference",
|
78
|
+
"author": "Nigel Rees",
|
79
|
+
"title": "Sayings of the Century",
|
80
|
+
"price": 8.95
|
81
|
+
},
|
82
|
+
{
|
83
|
+
"category": "fiction",
|
84
|
+
"author": "Evelyn Waugh",
|
85
|
+
"title": "Sword of Honour",
|
86
|
+
"price": 12.99
|
87
|
+
}
|
88
|
+
]
|
89
|
+
}
|
90
|
+
HERE_DOC
|
91
|
+
got = JsonPath.on(json, "$.store.book[*](category,author)")
|
92
|
+
expected = [
|
93
|
+
{
|
94
|
+
"category" => "reference",
|
95
|
+
"author" => "Nigel Rees"
|
96
|
+
},
|
97
|
+
{
|
98
|
+
"category" => "fiction",
|
99
|
+
"author" => "Evelyn Waugh"
|
100
|
+
}
|
101
|
+
]
|
102
|
+
assert_equal expected, got
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_manipulation_section
|
106
|
+
assert_equal({"candy" => "big turks"}, JsonPath.for('{"candy":"lollipop"}').gsub('$..candy') {|v| "big turks" }.to_hash)
|
107
|
+
|
108
|
+
json = '{"candy":"lollipop","noncandy":null,"other":"things"}'
|
109
|
+
o = JsonPath.for(json).
|
110
|
+
gsub('$..candy') {|v| "big turks" }.
|
111
|
+
compact.
|
112
|
+
delete('$..other').
|
113
|
+
to_hash
|
114
|
+
assert_equal({"candy" => "big turks"}, o)
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonpath
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Hull
|
8
8
|
- Gergely Brautigam
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2022-04-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: multi_json
|
@@ -81,6 +81,20 @@ dependencies:
|
|
81
81
|
- - ">="
|
82
82
|
- !ruby/object:Gem::Version
|
83
83
|
version: '0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: racc
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
84
98
|
- !ruby/object:Gem::Dependency
|
85
99
|
name: rake
|
86
100
|
requirement: !ruby/object:Gem::Requirement
|
@@ -106,11 +120,11 @@ extra_rdoc_files:
|
|
106
120
|
- README.md
|
107
121
|
files:
|
108
122
|
- ".gemtest"
|
123
|
+
- ".github/workflows/test.yml"
|
109
124
|
- ".gitignore"
|
110
125
|
- ".rspec"
|
111
126
|
- ".rubocop.yml"
|
112
127
|
- ".rubocop_todo.yml"
|
113
|
-
- ".travis.yml"
|
114
128
|
- Gemfile
|
115
129
|
- LICENSE.md
|
116
130
|
- README.md
|
@@ -118,17 +132,19 @@ files:
|
|
118
132
|
- bin/jsonpath
|
119
133
|
- jsonpath.gemspec
|
120
134
|
- lib/jsonpath.rb
|
135
|
+
- lib/jsonpath/dig.rb
|
121
136
|
- lib/jsonpath/enumerable.rb
|
122
137
|
- lib/jsonpath/parser.rb
|
123
138
|
- lib/jsonpath/proxy.rb
|
124
139
|
- lib/jsonpath/version.rb
|
125
140
|
- test/test_jsonpath.rb
|
126
141
|
- test/test_jsonpath_bin.rb
|
142
|
+
- test/test_readme.rb
|
127
143
|
homepage: https://github.com/joshbuddy/jsonpath
|
128
144
|
licenses:
|
129
145
|
- MIT
|
130
146
|
metadata: {}
|
131
|
-
post_install_message:
|
147
|
+
post_install_message:
|
132
148
|
rdoc_options: []
|
133
149
|
require_paths:
|
134
150
|
- lib
|
@@ -143,8 +159,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
143
159
|
- !ruby/object:Gem::Version
|
144
160
|
version: '0'
|
145
161
|
requirements: []
|
146
|
-
rubygems_version: 3.
|
147
|
-
signing_key:
|
162
|
+
rubygems_version: 3.3.7
|
163
|
+
signing_key:
|
148
164
|
specification_version: 4
|
149
165
|
summary: Ruby implementation of http://goessner.net/articles/JsonPath/
|
150
166
|
test_files: []
|