jsonpath 0.7.2 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|