jsonpath 0.7.2 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +0 -2
- data/lib/jsonpath.rb +1 -0
- data/lib/jsonpath/enumerable.rb +6 -34
- data/lib/jsonpath/parser.rb +62 -0
- data/lib/jsonpath/version.rb +1 -1
- data/test/test_jsonpath.rb +9 -4
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 250b6d90a78bf853330c06f41cacee3207c39e1cb5a13a0ea747f6511660fefb
|
4
|
+
data.tar.gz: 22a728dbfe6c1ca44884db61e4e2dc2e89c355b7fa4c308fb0bcfc9a2bb3e815
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44abf571b8d09b3dd4c5ec51a801ca064cfc08569a4121d58d456bd8403c3396b33f8f369992b258016374c8d823825f1619430c7434ebba2eec1bc19dd10b8c
|
7
|
+
data.tar.gz: 11ec6a115406f90775f3eb73b959befc1249e47d998b6679f5fcca671b04a694872282aa6ac6b79faad8d6fd1767c84b65abea536e0773518d76329ebb866ceb
|
data/README.md
CHANGED
@@ -103,8 +103,6 @@ enum.any?{ |c| c == 'red' }
|
|
103
103
|
# => true
|
104
104
|
```
|
105
105
|
|
106
|
-
You can optionally prevent eval from being called on sub-expressions by passing in :allow_eval => false to the constructor.
|
107
|
-
|
108
106
|
### More examples
|
109
107
|
|
110
108
|
For more usage examples and variations on paths, please visit the tests. There are some more complex ones as well.
|
data/lib/jsonpath.rb
CHANGED
data/lib/jsonpath/enumerable.rb
CHANGED
@@ -1,19 +1,12 @@
|
|
1
1
|
class JsonPath
|
2
2
|
class Enumerable
|
3
3
|
include ::Enumerable
|
4
|
-
attr_reader :allow_eval
|
5
|
-
alias_method :allow_eval?, :allow_eval
|
6
4
|
|
7
5
|
def initialize(path, object, mode, options = nil)
|
8
6
|
@path = path.path
|
9
7
|
@object = object
|
10
8
|
@mode = mode
|
11
9
|
@options = options
|
12
|
-
@allow_eval = if @options && @options.key?(:allow_eval)
|
13
|
-
@options[:allow_eval]
|
14
|
-
else
|
15
|
-
true
|
16
|
-
end
|
17
10
|
end
|
18
11
|
|
19
12
|
def each(context = @object, key = nil, pos = 0, &blk)
|
@@ -27,10 +20,6 @@ class JsonPath
|
|
27
20
|
each(context, key, pos + 1, &blk) if node == @object
|
28
21
|
when /^\[(.*)\]$/
|
29
22
|
handle_wildecard(node, expr, context, key, pos, &blk)
|
30
|
-
else
|
31
|
-
if pos == (@path.size - 1) && node && allow_eval?
|
32
|
-
yield_value(blk, context, key) if instance_eval("node #{@path[pos]}")
|
33
|
-
end
|
34
23
|
end
|
35
24
|
|
36
25
|
if pos > 0 && @path[pos - 1] == '..' || (@path[pos - 1] == '*' && @path[pos] != '..')
|
@@ -75,11 +64,12 @@ class JsonPath
|
|
75
64
|
end
|
76
65
|
|
77
66
|
def handle_question_mark(sub_path, node, pos, &blk)
|
78
|
-
raise 'Cannot use ?(...) unless eval is enabled' unless allow_eval?
|
79
67
|
case node
|
80
68
|
when Array
|
81
69
|
node.size.times do |index|
|
82
70
|
@_current_node = node[index]
|
71
|
+
# exps = sub_path[1, sub_path.size - 1]
|
72
|
+
# if @_current_node.send("[#{exps.gsub(/@/, '@_current_node')}]")
|
83
73
|
if process_function_or_literal(sub_path[1, sub_path.size - 1])
|
84
74
|
each(@_current_node, nil, pos + 1, &blk)
|
85
75
|
end
|
@@ -113,40 +103,22 @@ class JsonPath
|
|
113
103
|
def process_function_or_literal(exp, default = nil)
|
114
104
|
return default if exp.nil? || exp.empty?
|
115
105
|
return Integer(exp) if exp[0] != '('
|
116
|
-
return nil unless
|
106
|
+
return nil unless @_current_node
|
117
107
|
|
118
108
|
identifiers = /@?((?<!\d)\.(?!\d)(\w+))+/.match(exp)
|
119
|
-
# puts JsonPath.on(@_current_node, "#{identifiers}") unless identifiers.nil? ||
|
120
|
-
# @_current_node
|
121
|
-
# .methods
|
122
|
-
# .include?(identifiers[2].to_sym)
|
123
|
-
|
124
109
|
unless identifiers.nil? ||
|
125
110
|
@_current_node.methods.include?(identifiers[2].to_sym)
|
126
111
|
exp_to_eval = exp.dup
|
127
112
|
exp_to_eval[identifiers[0]] = identifiers[0].split('.').map do |el|
|
128
|
-
el == '@' ? '@
|
113
|
+
el == '@' ? '@' : "['#{el}']"
|
129
114
|
end.join
|
130
|
-
|
131
115
|
begin
|
132
|
-
return
|
133
|
-
# if eval failed because of bad arguments or missing methods
|
116
|
+
return JsonPath::Parser.new(@_current_node).parse(exp_to_eval)
|
134
117
|
rescue StandardError
|
135
118
|
return default
|
136
119
|
end
|
137
120
|
end
|
138
|
-
|
139
|
-
# otherwise eval as is
|
140
|
-
# TODO: this eval is wrong, because hash accessor could be nil and nil
|
141
|
-
# cannot be compared with anything, for instance,
|
142
|
-
# @a_current_node['price'] - we can't be sure that 'price' are in every
|
143
|
-
# node, but it's only in several nodes I wrapped this eval into rescue
|
144
|
-
# returning false when error, but this eval should be refactored.
|
145
|
-
begin
|
146
|
-
instance_eval(exp.gsub(/@/, '@_current_node'))
|
147
|
-
rescue
|
148
|
-
false
|
149
|
-
end
|
121
|
+
JsonPath::Parser.new(@_current_node).parse(exp)
|
150
122
|
end
|
151
123
|
end
|
152
124
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
class JsonPath
|
4
|
+
# Parser parses and evaluates an expression passed to @_current_node.
|
5
|
+
class Parser
|
6
|
+
def initialize(node)
|
7
|
+
@_current_node = node
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse(exp)
|
11
|
+
exps = exp.split(/(&&)|(\|\|)/)
|
12
|
+
ret = parse_exp(exps.shift)
|
13
|
+
exps.each_with_index do |item, index|
|
14
|
+
case item
|
15
|
+
when '&&'
|
16
|
+
ret &&= parse_exp(exps[index + 1])
|
17
|
+
when '||'
|
18
|
+
ret ||= parse_exp(exps[index + 1])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
ret
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_exp(exp)
|
25
|
+
exp = exp.gsub(/@/, '').gsub(/[\(\)]/, '')
|
26
|
+
scanner = StringScanner.new(exp)
|
27
|
+
elements = []
|
28
|
+
until scanner.eos?
|
29
|
+
if scanner.scan(/\./)
|
30
|
+
sym = scanner.scan(/\w+/)
|
31
|
+
op = scanner.scan(/./)
|
32
|
+
num = scanner.scan(/\d+/)
|
33
|
+
return @_current_node.send(sym.to_sym).send(op.to_sym, num.to_i)
|
34
|
+
end
|
35
|
+
if t = scanner.scan(/\['\w+'\]+/)
|
36
|
+
elements << t.gsub(/\[|\]|'|\s+/, '')
|
37
|
+
elsif t = scanner.scan(/\s+[<>=][<>=]?\s+?/)
|
38
|
+
operator = t
|
39
|
+
elsif t = scanner.scan(/(\s+)?'?(\w+)?[.,]?(\w+)?'?(\s+)?/) # @TODO: At this point I should trim somewhere...
|
40
|
+
operand = t.delete("'").strip
|
41
|
+
elsif t = scanner.scan(/.*/)
|
42
|
+
raise "Could not process symbol: #{t}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
el = dig(elements, @_current_node)
|
46
|
+
return false unless el
|
47
|
+
return true if operator.nil? && el
|
48
|
+
operand = operand.to_f if operand.to_i.to_s == operand || operand.to_f.to_s == operand
|
49
|
+
el.send(operator.strip, operand)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# @TODO: Remove this once JsonPath no longer supports ruby versions below 2.3
|
55
|
+
def dig(keys, hash)
|
56
|
+
return nil unless hash.key?(keys.first)
|
57
|
+
return hash.fetch(keys.first) if keys.size == 1
|
58
|
+
prev = keys.shift
|
59
|
+
dig(keys, hash.fetch(prev))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/jsonpath/version.rb
CHANGED
data/test/test_jsonpath.rb
CHANGED
@@ -80,8 +80,8 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
80
80
|
assert_equal [@object['store']['book'][1]], JsonPath.new("$..book[?(@['price'] < 23.0 && @['price'] > 9.0)]").on(@object)
|
81
81
|
end
|
82
82
|
|
83
|
-
def
|
84
|
-
assert_equal [], JsonPath.new(
|
83
|
+
def test_eval_with_floating_point
|
84
|
+
assert_equal [@object['store']['book'][1]], JsonPath.new("$..book[?(@['price'] == 13.0)]").on(@object)
|
85
85
|
end
|
86
86
|
|
87
87
|
def test_paths_with_underscores
|
@@ -109,7 +109,7 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
109
109
|
end
|
110
110
|
|
111
111
|
def test_counting
|
112
|
-
assert_equal
|
112
|
+
assert_equal 57, JsonPath.new('$..*').on(@object).to_a.size
|
113
113
|
end
|
114
114
|
|
115
115
|
def test_space_in_path
|
@@ -255,6 +255,10 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
255
255
|
JsonPath.new("$.store._links").on(@object)
|
256
256
|
end
|
257
257
|
|
258
|
+
# def test_filter_support_include
|
259
|
+
# #assert_equal true, JsonPath.new("$.store.book[(@.tags == 'asdf3')]").on(@object)
|
260
|
+
# assert_equal true, JsonPath.new("$.store.book..tags[?(@ == 'asdf')]").on(@object)
|
261
|
+
# end
|
258
262
|
|
259
263
|
def example_object
|
260
264
|
{ 'store' => {
|
@@ -262,7 +266,8 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
262
266
|
{ 'category' => 'reference',
|
263
267
|
'author' => 'Nigel Rees',
|
264
268
|
'title' => 'Sayings of the Century',
|
265
|
-
'price' => 9
|
269
|
+
'price' => 9,
|
270
|
+
'tags' => ['asdf', 'asdf2']},
|
266
271
|
{ 'category' => 'fiction',
|
267
272
|
'author' => 'Evelyn Waugh',
|
268
273
|
'title' => 'Sword of Honour',
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonpath
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Hull
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2017-05-
|
12
|
+
date: 2017-05-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: multi_json
|
@@ -117,6 +117,7 @@ files:
|
|
117
117
|
- jsonpath.gemspec
|
118
118
|
- lib/jsonpath.rb
|
119
119
|
- lib/jsonpath/enumerable.rb
|
120
|
+
- lib/jsonpath/parser.rb
|
120
121
|
- lib/jsonpath/proxy.rb
|
121
122
|
- lib/jsonpath/version.rb
|
122
123
|
- test/test_jsonpath.rb
|