jsonpath 0.5.8 → 0.7.0
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 +4 -4
- data/.travis.yml +2 -3
- data/LICENSE.md +21 -0
- data/README.md +9 -1
- data/Rakefile +2 -5
- data/bin/jsonpath +1 -1
- data/jsonpath.gemspec +12 -11
- data/lib/jsonpath.rb +29 -28
- data/lib/jsonpath/enumerable.rb +98 -80
- data/lib/jsonpath/proxy.rb +6 -5
- data/lib/jsonpath/version.rb +2 -2
- data/test/test_jsonpath.rb +133 -80
- data/test/test_jsonpath_bin.rb +7 -7
- metadata +9 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c8e5023499ffb2c83cb4eeed6d400f55f28bf8f
|
4
|
+
data.tar.gz: 81cb76411fd07a5d0ce6ef227d72682de7c9c4b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e3475b1e4bdf85fcc9da42a353f81db379a41cc8baf01680096083ffda68a40deee85872632f1470ff94285295830e7933d8656cf59e8a4ff48cc860d89049e1
|
7
|
+
data.tar.gz: 7a89006179792222b0222a599fae3ceb4548d49b73f9cd89867716f29b567547f36edd5251bd1bc9d7ddb58f0d4150f87f9860021b587b04c0cff0183b45ddb5
|
data/.travis.yml
CHANGED
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Joshua Lin & Gergely Brautigam
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -81,7 +81,7 @@ JsonPath.new('$..book[::2]').on(json)
|
|
81
81
|
...and evals.
|
82
82
|
|
83
83
|
~~~~~ {ruby}
|
84
|
-
JsonPath.new('$..price[?(@ <
|
84
|
+
JsonPath.new('$..price[?(@ < 10)]').on(json)
|
85
85
|
# => [8.95, 8.99]
|
86
86
|
~~~~~
|
87
87
|
|
@@ -105,6 +105,10 @@ enum.any?{ |c| c == 'red' }
|
|
105
105
|
|
106
106
|
You can optionally prevent eval from being called on sub-expressions by passing in :allow_eval => false to the constructor.
|
107
107
|
|
108
|
+
### More examples
|
109
|
+
|
110
|
+
For more usage examples and variations on paths, please visit the tests. There are some more complex ones as well.
|
111
|
+
|
108
112
|
### Manipulation
|
109
113
|
|
110
114
|
If you'd like to do substitution in a json object, you can use `#gsub` or `#gsub!` to modify the object in place.
|
@@ -130,3 +134,7 @@ o = JsonPath.for(json).
|
|
130
134
|
to_hash
|
131
135
|
# => {"candy" => "big turks"}
|
132
136
|
~~~~~
|
137
|
+
|
138
|
+
# Contributions
|
139
|
+
|
140
|
+
Please feel free to submit an Issue or a Pull Request any time you feel like you would like to contribute. Thank you!
|
data/Rakefile
CHANGED
@@ -2,11 +2,8 @@ require 'bundler'
|
|
2
2
|
Bundler::GemHelper.install_tasks
|
3
3
|
|
4
4
|
task :test do
|
5
|
-
|
6
|
-
require 'minitest/autorun'
|
7
|
-
require 'phocus'
|
8
|
-
require 'jsonpath'
|
5
|
+
$LOAD_PATH << 'lib'
|
9
6
|
Dir['./test/**/test_*.rb'].each { |test| require test }
|
10
7
|
end
|
11
8
|
|
12
|
-
task :
|
9
|
+
task default: :test
|
data/bin/jsonpath
CHANGED
@@ -15,7 +15,7 @@ usage unless ARGV[0]
|
|
15
15
|
|
16
16
|
jsonpath = JsonPath.new(ARGV[0])
|
17
17
|
case ARGV[1]
|
18
|
-
when nil #stdin
|
18
|
+
when nil # stdin
|
19
19
|
puts MultiJson.encode(jsonpath.on(MultiJson.decode(STDIN.read)))
|
20
20
|
when String
|
21
21
|
puts MultiJson.encode(jsonpath.on(MultiJson.decode(File.exist?(ARGV[1]) ? File.read(ARGV[1]) : ARGV[1])))
|
data/jsonpath.gemspec
CHANGED
@@ -5,20 +5,21 @@ require File.join(File.dirname(__FILE__), 'lib', 'jsonpath', 'version')
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = 'jsonpath'
|
7
7
|
s.version = JsonPath::VERSION
|
8
|
-
s.required_rubygems_version =
|
9
|
-
|
10
|
-
s.
|
11
|
-
s.
|
12
|
-
s.
|
8
|
+
s.required_rubygems_version =
|
9
|
+
Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
|
10
|
+
s.authors = ['Joshua Hull', 'Gergely Brautigam']
|
11
|
+
s.summary = 'Ruby implementation of http://goessner.net/articles/JsonPath/'
|
12
|
+
s.description = 'Ruby implementation of http://goessner.net/articles/JsonPath/.'
|
13
|
+
s.email = ['joshbuddy@gmail.com', 'skarlso777@gmail.com']
|
13
14
|
s.extra_rdoc_files = ['README.md']
|
14
15
|
s.files = `git ls-files`.split("\n")
|
15
|
-
s.homepage =
|
16
|
-
s.rdoc_options = [
|
17
|
-
s.require_paths = [
|
18
|
-
s.rubygems_version =
|
19
|
-
s.test_files = `git ls-files`.split("\n").select{|f| f =~ /^spec/}
|
16
|
+
s.homepage = 'https://github.com/joshbuddy/jsonpath'
|
17
|
+
s.rdoc_options = ['--charset=UTF-8']
|
18
|
+
s.require_paths = ['lib']
|
19
|
+
s.rubygems_version = '1.3.7'
|
20
|
+
s.test_files = `git ls-files`.split("\n").select { |f| f =~ /^spec/ }
|
20
21
|
s.rubyforge_project = 'jsonpath'
|
21
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
22
23
|
s.licenses = ['MIT']
|
23
24
|
|
24
25
|
# dependencies
|
data/lib/jsonpath.rb
CHANGED
@@ -4,9 +4,10 @@ require 'jsonpath/proxy'
|
|
4
4
|
require 'jsonpath/enumerable'
|
5
5
|
require 'jsonpath/version'
|
6
6
|
|
7
|
+
# JsonPath: initializes the class with a given JsonPath and parses that path
|
8
|
+
# into a token array.
|
7
9
|
class JsonPath
|
8
|
-
|
9
|
-
PATH_ALL = '$..*'
|
10
|
+
PATH_ALL = '$..*'.freeze
|
10
11
|
|
11
12
|
attr_accessor :path
|
12
13
|
|
@@ -14,39 +15,19 @@ class JsonPath
|
|
14
15
|
@opts = opts
|
15
16
|
scanner = StringScanner.new(path)
|
16
17
|
@path = []
|
17
|
-
|
18
|
-
if token = scanner.scan(
|
19
|
-
@path << token
|
20
|
-
elsif token = scanner.scan(/@/)
|
18
|
+
until scanner.eos?
|
19
|
+
if token = scanner.scan(/\$|@\B|\*|\.\./)
|
21
20
|
@path << token
|
22
|
-
elsif token = scanner.scan(/[a-zA-Z0-
|
21
|
+
elsif token = scanner.scan(/[\$@a-zA-Z0-9:_-]+/)
|
23
22
|
@path << "['#{token}']"
|
24
23
|
elsif token = scanner.scan(/'(.*?)'/)
|
25
24
|
@path << "[#{token}]"
|
26
25
|
elsif token = scanner.scan(/\[/)
|
27
|
-
|
28
|
-
while !count.zero?
|
29
|
-
if t = scanner.scan(/\[/)
|
30
|
-
token << t
|
31
|
-
count += 1
|
32
|
-
elsif t = scanner.scan(/\]/)
|
33
|
-
token << t
|
34
|
-
count -= 1
|
35
|
-
elsif t = scanner.scan(/[^\[\]]+/)
|
36
|
-
token << t
|
37
|
-
elsif scanner.eos?
|
38
|
-
raise ArgumentError, 'unclosed bracket'
|
39
|
-
end
|
40
|
-
end
|
41
|
-
@path << token
|
26
|
+
@path << find_matching_brackets(token, scanner)
|
42
27
|
elsif token = scanner.scan(/\]/)
|
43
28
|
raise ArgumentError, 'unmatched closing bracket'
|
44
|
-
elsif token = scanner.scan(/\.\./)
|
45
|
-
@path << token
|
46
29
|
elsif scanner.scan(/\./)
|
47
30
|
nil
|
48
|
-
elsif token = scanner.scan(/\*/)
|
49
|
-
@path << token
|
50
31
|
elsif token = scanner.scan(/[><=] \d+/)
|
51
32
|
@path.last << token
|
52
33
|
elsif token = scanner.scan(/./)
|
@@ -55,6 +36,24 @@ class JsonPath
|
|
55
36
|
end
|
56
37
|
end
|
57
38
|
|
39
|
+
def find_matching_brackets(token, scanner)
|
40
|
+
count = 1
|
41
|
+
until count.zero?
|
42
|
+
if t = scanner.scan(/\[/)
|
43
|
+
token << t
|
44
|
+
count += 1
|
45
|
+
elsif t = scanner.scan(/\]/)
|
46
|
+
token << t
|
47
|
+
count -= 1
|
48
|
+
elsif t = scanner.scan(/[^\[\]]+/)
|
49
|
+
token << t
|
50
|
+
elsif scanner.eos?
|
51
|
+
raise ArgumentError, 'unclosed bracket'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
token
|
55
|
+
end
|
56
|
+
|
58
57
|
def join(join_path)
|
59
58
|
res = deep_clone
|
60
59
|
res.path += JsonPath.new(join_path).path
|
@@ -70,12 +69,13 @@ class JsonPath
|
|
70
69
|
end
|
71
70
|
|
72
71
|
def enum_on(obj_or_str, mode = nil)
|
73
|
-
JsonPath::Enumerable.new(self, self.class.process_object(obj_or_str), mode,
|
72
|
+
JsonPath::Enumerable.new(self, self.class.process_object(obj_or_str), mode,
|
73
|
+
@opts)
|
74
74
|
end
|
75
75
|
alias_method :[], :enum_on
|
76
76
|
|
77
77
|
def self.on(obj_or_str, path, opts = nil)
|
78
|
-
|
78
|
+
new(path, opts).on(process_object(obj_or_str))
|
79
79
|
end
|
80
80
|
|
81
81
|
def self.for(obj_or_str)
|
@@ -83,6 +83,7 @@ class JsonPath
|
|
83
83
|
end
|
84
84
|
|
85
85
|
private
|
86
|
+
|
86
87
|
def self.process_object(obj_or_str)
|
87
88
|
obj_or_str.is_a?(String) ? MultiJson.decode(obj_or_str) : obj_or_str
|
88
89
|
end
|
data/lib/jsonpath/enumerable.rb
CHANGED
@@ -5,8 +5,15 @@ class JsonPath
|
|
5
5
|
alias_method :allow_eval?, :allow_eval
|
6
6
|
|
7
7
|
def initialize(path, object, mode, options = nil)
|
8
|
-
@path
|
9
|
-
@
|
8
|
+
@path = path.path
|
9
|
+
@object = object
|
10
|
+
@mode = mode
|
11
|
+
@options = options
|
12
|
+
@allow_eval = if @options && @options.key?(:allow_eval)
|
13
|
+
@options[:allow_eval]
|
14
|
+
else
|
15
|
+
true
|
16
|
+
end
|
10
17
|
end
|
11
18
|
|
12
19
|
def each(context = @object, key = nil, pos = 0, &blk)
|
@@ -14,74 +21,78 @@ class JsonPath
|
|
14
21
|
@_current_node = node
|
15
22
|
return yield_value(blk, context, key) if pos == @path.size
|
16
23
|
case expr = @path[pos]
|
17
|
-
when '*', '..'
|
24
|
+
when '*', '..', '@'
|
18
25
|
each(context, key, pos + 1, &blk)
|
19
26
|
when '$'
|
20
27
|
each(context, key, pos + 1, &blk) if node == @object
|
21
|
-
when '@'
|
22
|
-
each(context, key, pos + 1, &blk)
|
23
28
|
when /^\[(.*)\]$/
|
24
|
-
expr
|
25
|
-
case sub_path[0]
|
26
|
-
when ?', ?"
|
27
|
-
if node.is_a?(Hash)
|
28
|
-
k = sub_path[1,sub_path.size - 2]
|
29
|
-
each(node, k, pos + 1, &blk) if node.key?(k)
|
30
|
-
end
|
31
|
-
when ??
|
32
|
-
raise "Cannot use ?(...) unless eval is enabled" unless allow_eval?
|
33
|
-
case node
|
34
|
-
when Hash, Array
|
35
|
-
(node.is_a?(Hash) ? node.keys : (0..node.size)).each do |e|
|
36
|
-
@_current_node = node[e]
|
37
|
-
if process_function_or_literal(sub_path[1, sub_path.size - 1])
|
38
|
-
each(@_current_node, nil, pos + 1, &blk)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
else
|
42
|
-
yield node if process_function_or_literal(sub_path[1, sub_path.size - 1])
|
43
|
-
end
|
44
|
-
else
|
45
|
-
if node.is_a?(Array)
|
46
|
-
next if node.empty?
|
47
|
-
array_args = sub_path.split(':')
|
48
|
-
if array_args[0] == ?*
|
49
|
-
start_idx = 0
|
50
|
-
end_idx = node.size - 1
|
51
|
-
else
|
52
|
-
start_idx = process_function_or_literal(array_args[0], 0)
|
53
|
-
next unless start_idx
|
54
|
-
end_idx = (array_args[1] && process_function_or_literal(array_args[1], -1) || (sub_path.count(':') == 0 ? start_idx : -1))
|
55
|
-
next unless end_idx
|
56
|
-
if start_idx == end_idx
|
57
|
-
next unless start_idx < node.size
|
58
|
-
end
|
59
|
-
end
|
60
|
-
start_idx %= node.size
|
61
|
-
end_idx %= node.size
|
62
|
-
step = process_function_or_literal(array_args[2], 1)
|
63
|
-
next unless step
|
64
|
-
(start_idx..end_idx).step(step) {|i| each(node, i, pos + 1, &blk)}
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
29
|
+
handle_wildecard(node, expr, context, key, pos, &blk)
|
68
30
|
else
|
69
31
|
if pos == (@path.size - 1) && node && allow_eval?
|
70
|
-
if
|
71
|
-
yield_value(blk, context, key)
|
72
|
-
end
|
32
|
+
yield_value(blk, context, key) if instance_eval("node #{@path[pos]}")
|
73
33
|
end
|
74
34
|
end
|
75
35
|
|
76
|
-
if pos > 0 && @path[pos-1] == '..'
|
36
|
+
if pos > 0 && @path[pos - 1] == '..' || (@path[pos - 1] == '*' && @path[pos] != '..')
|
77
37
|
case node
|
78
|
-
when Hash then node.each {|k,
|
79
|
-
when Array then node.each_with_index {|
|
38
|
+
when Hash then node.each { |k, _| each(node, k, pos, &blk) }
|
39
|
+
when Array then node.each_with_index { |_, i| each(node, i, pos, &blk) }
|
80
40
|
end
|
81
41
|
end
|
82
42
|
end
|
83
43
|
|
84
44
|
private
|
45
|
+
|
46
|
+
def handle_wildecard(node, expr, context, key, pos, &blk)
|
47
|
+
expr[1, expr.size - 2].split(',').each do |sub_path|
|
48
|
+
case sub_path[0]
|
49
|
+
when '\'', '"'
|
50
|
+
next unless node.is_a?(Hash)
|
51
|
+
k = sub_path[1, sub_path.size - 2]
|
52
|
+
each(node, k, pos + 1, &blk) if node.key?(k)
|
53
|
+
when '?'
|
54
|
+
handle_question_mark(sub_path, node, pos, &blk)
|
55
|
+
else
|
56
|
+
next unless node.is_a?(Array) && !node.empty?
|
57
|
+
array_args = sub_path.split(':')
|
58
|
+
if array_args[0] == '*'
|
59
|
+
start_idx = 0
|
60
|
+
end_idx = node.size - 1
|
61
|
+
else
|
62
|
+
start_idx = process_function_or_literal(array_args[0], 0)
|
63
|
+
next unless start_idx
|
64
|
+
end_idx = (array_args[1] && process_function_or_literal(array_args[1], -1) || (sub_path.count(':') == 0 ? start_idx : -1))
|
65
|
+
next unless end_idx
|
66
|
+
next if start_idx == end_idx && start_idx >= node.size
|
67
|
+
end
|
68
|
+
start_idx %= node.size
|
69
|
+
end_idx %= node.size
|
70
|
+
step = process_function_or_literal(array_args[2], 1)
|
71
|
+
next unless step
|
72
|
+
(start_idx..end_idx).step(step) { |i| each(node, i, pos + 1, &blk) }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle_question_mark(sub_path, node, pos, &blk)
|
78
|
+
raise 'Cannot use ?(...) unless eval is enabled' unless allow_eval?
|
79
|
+
case node
|
80
|
+
when Array
|
81
|
+
node.size.times do |index|
|
82
|
+
@_current_node = node[index]
|
83
|
+
if process_function_or_literal(sub_path[1, sub_path.size - 1])
|
84
|
+
each(@_current_node, nil, pos + 1, &blk)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
when Hash
|
88
|
+
if process_function_or_literal(sub_path[1, sub_path.size - 1])
|
89
|
+
each(@_current_node, nil, pos + 1, &blk)
|
90
|
+
end
|
91
|
+
else
|
92
|
+
yield node if process_function_or_literal(sub_path[1, sub_path.size - 1])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
85
96
|
def yield_value(blk, context, key)
|
86
97
|
case @mode
|
87
98
|
when nil
|
@@ -100,35 +111,42 @@ class JsonPath
|
|
100
111
|
end
|
101
112
|
|
102
113
|
def process_function_or_literal(exp, default = nil)
|
103
|
-
if exp.nil?
|
104
|
-
|
105
|
-
|
106
|
-
return nil unless allow_eval? && @_current_node
|
107
|
-
identifiers = /@?(\.(\w+))+/.match(exp)
|
114
|
+
return default if exp.nil? || exp.empty?
|
115
|
+
return Integer(exp) if exp[0] != '('
|
116
|
+
return nil unless allow_eval? && @_current_node
|
108
117
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
+
identifiers = /@?(\.(\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
|
+
unless identifiers.nil? ||
|
125
|
+
@_current_node.methods.include?(identifiers[2].to_sym)
|
126
|
+
|
127
|
+
exp_to_eval = exp.dup
|
128
|
+
exp_to_eval[identifiers[0]] = identifiers[0].split('.').map do |el|
|
129
|
+
el == '@' ? '@_current_node' : "['#{el}']"
|
130
|
+
end.join
|
118
131
|
|
119
|
-
# otherwise eval as is
|
120
|
-
# TODO: this eval is wrong, because hash accessor could be nil and nil cannot be compared with anything,
|
121
|
-
# for instance, @_current_node['price'] - we can't be sure that 'price' are in every node, but it's only in several nodes
|
122
|
-
# I wrapped this eval into rescue returning false when error, but this eval should be refactored.
|
123
132
|
begin
|
124
|
-
|
125
|
-
|
126
|
-
|
133
|
+
return instance_eval(exp_to_eval)
|
134
|
+
# if eval failed because of bad arguments or missing methods
|
135
|
+
rescue StandardError
|
136
|
+
return default
|
127
137
|
end
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
138
|
+
end
|
139
|
+
|
140
|
+
# otherwise eval as is
|
141
|
+
# TODO: this eval is wrong, because hash accessor could be nil and nil
|
142
|
+
# cannot be compared with anything, for instance,
|
143
|
+
# @a_current_node['price'] - we can't be sure that 'price' are in every
|
144
|
+
# node, but it's only in several nodes I wrapped this eval into rescue
|
145
|
+
# returning false when error, but this eval should be refactored.
|
146
|
+
begin
|
147
|
+
instance_eval(exp.gsub(/@/, '@_current_node'))
|
148
|
+
rescue
|
149
|
+
false
|
132
150
|
end
|
133
151
|
end
|
134
152
|
end
|
data/lib/jsonpath/proxy.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
class JsonPath
|
2
2
|
class Proxy
|
3
3
|
attr_reader :obj
|
4
|
-
|
4
|
+
alias to_hash obj
|
5
5
|
|
6
6
|
def initialize(obj)
|
7
7
|
@obj = obj
|
8
8
|
end
|
9
9
|
|
10
10
|
def gsub(path, replacement = nil, &replacement_block)
|
11
|
-
_gsub(_deep_copy, path, replacement ? proc{replacement} : replacement_block)
|
11
|
+
_gsub(_deep_copy, path, replacement ? proc { replacement } : replacement_block)
|
12
12
|
end
|
13
13
|
|
14
14
|
def gsub!(path, replacement = nil, &replacement_block)
|
15
|
-
_gsub(@obj, path, replacement ? proc{replacement} : replacement_block)
|
15
|
+
_gsub(@obj, path, replacement ? proc { replacement } : replacement_block)
|
16
16
|
end
|
17
17
|
|
18
18
|
def delete(path = JsonPath::PATH_ALL)
|
@@ -32,8 +32,9 @@ class JsonPath
|
|
32
32
|
end
|
33
33
|
|
34
34
|
private
|
35
|
+
|
35
36
|
def _deep_copy
|
36
|
-
Marshal
|
37
|
+
Marshal.load(Marshal.dump(@obj))
|
37
38
|
end
|
38
39
|
|
39
40
|
def _gsub(obj, path, replacement)
|
@@ -51,4 +52,4 @@ class JsonPath
|
|
51
52
|
Proxy.new(obj)
|
52
53
|
end
|
53
54
|
end
|
54
|
-
end
|
55
|
+
end
|
data/lib/jsonpath/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
class JsonPath
|
2
|
-
VERSION = '0.
|
3
|
-
end
|
2
|
+
VERSION = '0.7.0'.freeze
|
3
|
+
end
|
data/test/test_jsonpath.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'phocus'
|
3
|
+
require 'jsonpath'
|
2
4
|
|
5
|
+
class TestJsonpath < MiniTest::Unit::TestCase
|
3
6
|
def setup
|
4
7
|
@object = example_object
|
5
8
|
@object2 = example_object
|
6
9
|
end
|
7
10
|
|
8
11
|
def test_bracket_matching
|
9
|
-
assert_raises(ArgumentError) {
|
10
|
-
|
11
|
-
|
12
|
-
assert_raises(ArgumentError) {
|
13
|
-
JsonPath.new('$.store.book[0]]')
|
14
|
-
}
|
12
|
+
assert_raises(ArgumentError) { JsonPath.new('$.store.book[0') }
|
13
|
+
assert_raises(ArgumentError) { JsonPath.new('$.store.book[0]]') }
|
14
|
+
assert_equal [9], JsonPath.new('$.store.book[0].price').on(@object)
|
15
15
|
end
|
16
16
|
|
17
17
|
def test_lookup_direct_path
|
@@ -66,12 +66,12 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
66
66
|
|
67
67
|
if RUBY_VERSION[/^1\.9/]
|
68
68
|
def test_recognize_filters_on_val
|
69
|
-
assert_equal [@object['store']['book'][1]['price'], @object['store']['book'][3]['price'], @object['store']['bicycle']['price']], JsonPath.new(
|
69
|
+
assert_equal [@object['store']['book'][1]['price'], @object['store']['book'][3]['price'], @object['store']['bicycle']['price']], JsonPath.new('$..price[?(@ > 10)]').on(@object)
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
73
|
def test_no_eval
|
74
|
-
assert_equal [], JsonPath.new('$..book[(@.length-2)]', :
|
74
|
+
assert_equal [], JsonPath.new('$..book[(@.length-2)]', allow_eval: false).on(@object)
|
75
75
|
end
|
76
76
|
|
77
77
|
def test_paths_with_underscores
|
@@ -79,7 +79,11 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
79
79
|
end
|
80
80
|
|
81
81
|
def test_path_with_hyphens
|
82
|
-
|
82
|
+
assert_equal [@object['store']['bicycle']['single-speed']], JsonPath.new('$.store.bicycle.single-speed').on(@object)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_path_with_colon
|
86
|
+
assert_equal [@object['store']['bicycle']['make:model']], JsonPath.new('$.store.bicycle.make:model').on(@object)
|
83
87
|
end
|
84
88
|
|
85
89
|
def test_paths_with_numbers
|
@@ -95,11 +99,11 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
95
99
|
end
|
96
100
|
|
97
101
|
def test_counting
|
98
|
-
assert_equal
|
102
|
+
assert_equal 50, JsonPath.new('$..*').on(@object).to_a.size
|
99
103
|
end
|
100
104
|
|
101
105
|
def test_space_in_path
|
102
|
-
assert_equal ['e'], JsonPath.new("$.'c d'").on(
|
106
|
+
assert_equal ['e'], JsonPath.new("$.'c d'").on('a' => 'a', 'b' => 'b', 'c d' => 'e')
|
103
107
|
end
|
104
108
|
|
105
109
|
def test_class_method
|
@@ -129,103 +133,152 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
129
133
|
end
|
130
134
|
|
131
135
|
def test_weird_gsub!
|
132
|
-
h = {'hi' => 'there'}
|
133
|
-
JsonPath.for(@object).gsub!('$.*') { |
|
136
|
+
h = { 'hi' => 'there' }
|
137
|
+
JsonPath.for(@object).gsub!('$.*') { |_| h }
|
134
138
|
assert_equal h, @object
|
135
139
|
end
|
136
140
|
|
141
|
+
def test_gsub_to_false!
|
142
|
+
h = { 'hi' => 'there' }
|
143
|
+
h2 = { 'hi' => false }
|
144
|
+
assert_equal h2, JsonPath.for(h).gsub!('$.hi') { |_| false }.to_hash
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_where_selector
|
148
|
+
JsonPath.for(@object).gsub!('$..book.price[?(@ > 20)]') { |p| p + 10 }
|
149
|
+
end
|
150
|
+
|
137
151
|
def test_compact
|
138
|
-
h = {'hi' => 'there', 'you' => nil}
|
152
|
+
h = { 'hi' => 'there', 'you' => nil }
|
139
153
|
JsonPath.for(h).compact!
|
140
|
-
assert_equal({'hi' => 'there'}, h)
|
154
|
+
assert_equal({ 'hi' => 'there' }, h)
|
141
155
|
end
|
142
156
|
|
143
157
|
def test_delete
|
144
|
-
h = {'hi' => 'there', 'you' => nil}
|
158
|
+
h = { 'hi' => 'there', 'you' => nil }
|
145
159
|
JsonPath.for(h).delete!('*.hi')
|
146
|
-
assert_equal({'you' => nil}, h)
|
160
|
+
assert_equal({ 'you' => nil }, h)
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_at_sign_in_json_element
|
164
|
+
data =
|
165
|
+
{ '@colors' =>
|
166
|
+
[{ '@r' => 255, '@g' => 0, '@b' => 0 },
|
167
|
+
{ '@r' => 0, '@g' => 255, '@b' => 0 },
|
168
|
+
{ '@r' => 0, '@g' => 0, '@b' => 255 }] }
|
169
|
+
|
170
|
+
assert_equal [255, 0, 0], JsonPath.on(data, '$..@r')
|
147
171
|
end
|
148
172
|
|
149
173
|
def test_wildcard
|
150
|
-
assert_equal @object['store']['book'].collect{|e| e['price']}.compact, JsonPath.on(@object, '$..book[*].price')
|
174
|
+
assert_equal @object['store']['book'].collect { |e| e['price'] }.compact, JsonPath.on(@object, '$..book[*].price')
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_wildcard_on_intermediary_element
|
178
|
+
assert_equal [1], JsonPath.on({ 'a' => { 'b' => { 'c' => 1 } } }, '$.a..c')
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_wildcard_on_intermediary_element_v2
|
182
|
+
assert_equal [1], JsonPath.on({ 'a' => { 'b' => { 'd' => { 'c' => 1 } } } }, '$.a..c')
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_wildcard_on_intermediary_element_v3
|
186
|
+
assert_equal [1], JsonPath.on({ 'a' => { 'b' => { 'd' => { 'c' => 1 } } } }, '$.a.*..c')
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_wildcard_on_intermediary_element_v4
|
190
|
+
assert_equal [1], JsonPath.on({ 'a' => { 'b' => { 'd' => { 'c' => 1 } } } }, '$.a.*..c')
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_wildcard_on_intermediary_element_v5
|
194
|
+
assert_equal [1], JsonPath.on({ 'a' => { 'b' => { 'c' => 1 } } }, '$.a.*.c')
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_wildcard_on_intermediary_element_v6
|
198
|
+
assert_equal ['red'], JsonPath.new('$.store.*.color').on(@object)
|
151
199
|
end
|
152
200
|
|
153
201
|
def test_wildcard_empty_array
|
154
|
-
object = @object.merge(
|
155
|
-
assert_equal [], JsonPath.on(object,
|
202
|
+
object = @object.merge('bicycle' => { 'tire' => [] })
|
203
|
+
assert_equal [], JsonPath.on(object, '$..bicycle.tire[*]')
|
156
204
|
end
|
157
205
|
|
158
|
-
def
|
159
|
-
assert_equal [@object['store']['book'][3]], JsonPath.new(
|
206
|
+
def test_support_filter_by_array_childnode_value
|
207
|
+
assert_equal [@object['store']['book'][3]], JsonPath.new('$..book[?(@.price > 20)]').on(@object)
|
160
208
|
end
|
161
209
|
|
162
210
|
def test_support_filter_by_childnode_value_with_inconsistent_children
|
163
|
-
@object['store']['book'][0] =
|
164
|
-
assert_equal [@object['store']['book'][3]], JsonPath.new(
|
211
|
+
@object['store']['book'][0] = 'string_instead_of_object'
|
212
|
+
assert_equal [@object['store']['book'][3]], JsonPath.new('$..book[?(@.price > 20)]').on(@object)
|
165
213
|
end
|
166
214
|
|
167
215
|
def test_support_filter_by_childnode_value_and_select_child_key
|
168
|
-
assert_equal [23], JsonPath.new(
|
216
|
+
assert_equal [23], JsonPath.new('$..book[?(@.price > 20)].price').on(@object)
|
169
217
|
end
|
170
218
|
|
171
219
|
def test_support_filter_by_childnode_value_over_childnode_and_select_child_key
|
172
|
-
assert_equal [
|
220
|
+
assert_equal ['Osennie Vizity'], JsonPath.new('$..book[?(@.written.year == 1996)].title').on(@object)
|
173
221
|
end
|
174
|
-
|
222
|
+
|
223
|
+
def test_support_filter_by_object_childnode_value
|
224
|
+
data = {
|
225
|
+
'data' => {
|
226
|
+
'type' => 'users',
|
227
|
+
'id' => '123'
|
228
|
+
}
|
229
|
+
}
|
230
|
+
assert_equal [{ 'type' => 'users', 'id' => '123' }], JsonPath.new("$.data[?(@.type == 'users')]").on(data)
|
231
|
+
assert_equal [], JsonPath.new("$.data[?(@.type == 'admins')]").on(data)
|
232
|
+
end
|
233
|
+
|
175
234
|
def example_object
|
176
|
-
{
|
177
|
-
|
178
|
-
{
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
{ "category"=> "russian_fiction",
|
215
|
-
"author"=> "Lukyanenko",
|
216
|
-
"title"=> "Ne vremya dlya drakonov",
|
217
|
-
"written" => {
|
218
|
-
"year" => 1997
|
219
|
-
}
|
220
|
-
}
|
235
|
+
{ 'store' => {
|
236
|
+
'book' => [
|
237
|
+
{ 'category' => 'reference',
|
238
|
+
'author' => 'Nigel Rees',
|
239
|
+
'title' => 'Sayings of the Century',
|
240
|
+
'price' => 9 },
|
241
|
+
{ 'category' => 'fiction',
|
242
|
+
'author' => 'Evelyn Waugh',
|
243
|
+
'title' => 'Sword of Honour',
|
244
|
+
'price' => 13 },
|
245
|
+
{ 'category' => 'fiction',
|
246
|
+
'author' => 'Herman Melville',
|
247
|
+
'title' => 'Moby Dick',
|
248
|
+
'isbn' => '0-553-21311-3',
|
249
|
+
'price' => 9 },
|
250
|
+
{ 'category' => 'fiction',
|
251
|
+
'author' => 'J. R. R. Tolkien',
|
252
|
+
'title' => 'The Lord of the Rings',
|
253
|
+
'isbn' => '0-395-19395-8',
|
254
|
+
'price' => 23 },
|
255
|
+
{ 'category' => 'russian_fiction',
|
256
|
+
'author' => 'Lukyanenko',
|
257
|
+
'title' => 'Imperatory Illuziy',
|
258
|
+
'written' => {
|
259
|
+
'year' => 1995
|
260
|
+
} },
|
261
|
+
{ 'category' => 'russian_fiction',
|
262
|
+
'author' => 'Lukyanenko',
|
263
|
+
'title' => 'Osennie Vizity',
|
264
|
+
'written' => {
|
265
|
+
'year' => 1996
|
266
|
+
} },
|
267
|
+
{ 'category' => 'russian_fiction',
|
268
|
+
'author' => 'Lukyanenko',
|
269
|
+
'title' => 'Ne vremya dlya drakonov',
|
270
|
+
'written' => {
|
271
|
+
'year' => 1997
|
272
|
+
} }
|
221
273
|
],
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
274
|
+
'bicycle' => {
|
275
|
+
'color' => 'red',
|
276
|
+
'price' => 20,
|
277
|
+
'catalogue_number' => 123_45,
|
278
|
+
'single-speed' => 'no',
|
279
|
+
'2seater' => 'yes',
|
280
|
+
'make:model' => 'Zippy Sweetwheeler'
|
281
|
+
}
|
228
282
|
} }
|
229
283
|
end
|
230
|
-
|
231
284
|
end
|
data/test/test_jsonpath_bin.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'phocus'
|
3
|
+
require 'jsonpath'
|
4
|
+
|
1
5
|
class TestJsonpathBin < MiniTest::Unit::TestCase
|
2
6
|
def setup
|
3
|
-
@runner =
|
7
|
+
@runner = 'ruby -Ilib bin/jsonpath'
|
4
8
|
@original_dir = Dir.pwd
|
5
9
|
Dir.chdir(File.join(File.dirname(__FILE__), '..'))
|
6
10
|
end
|
@@ -11,11 +15,7 @@ class TestJsonpathBin < MiniTest::Unit::TestCase
|
|
11
15
|
end
|
12
16
|
|
13
17
|
def test_stdin
|
14
|
-
|
15
|
-
end
|
16
|
-
|
17
|
-
def test_stdin
|
18
|
-
File.open('/tmp/test.json', 'w'){|f| f << '{"test": "time"}'}
|
18
|
+
File.open('/tmp/test.json', 'w') { |f| f << '{"test": "time"}' }
|
19
19
|
assert_equal '["time"]', `#{@runner} '$.test' /tmp/test.json`.strip
|
20
20
|
end
|
21
|
-
end
|
21
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonpath
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joshua Hull
|
8
|
+
- Gergely Brautigam
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2017-05-02 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: multi_json
|
@@ -95,7 +96,9 @@ dependencies:
|
|
95
96
|
- !ruby/object:Gem::Version
|
96
97
|
version: '0'
|
97
98
|
description: Ruby implementation of http://goessner.net/articles/JsonPath/.
|
98
|
-
email:
|
99
|
+
email:
|
100
|
+
- joshbuddy@gmail.com
|
101
|
+
- skarlso777@gmail.com
|
99
102
|
executables:
|
100
103
|
- jsonpath
|
101
104
|
extensions: []
|
@@ -107,6 +110,7 @@ files:
|
|
107
110
|
- ".rspec"
|
108
111
|
- ".travis.yml"
|
109
112
|
- Gemfile
|
113
|
+
- LICENSE.md
|
110
114
|
- README.md
|
111
115
|
- Rakefile
|
112
116
|
- bin/jsonpath
|
@@ -117,7 +121,7 @@ files:
|
|
117
121
|
- lib/jsonpath/version.rb
|
118
122
|
- test/test_jsonpath.rb
|
119
123
|
- test/test_jsonpath_bin.rb
|
120
|
-
homepage:
|
124
|
+
homepage: https://github.com/joshbuddy/jsonpath
|
121
125
|
licenses:
|
122
126
|
- MIT
|
123
127
|
metadata: {}
|
@@ -138,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
142
|
version: '0'
|
139
143
|
requirements: []
|
140
144
|
rubyforge_project: jsonpath
|
141
|
-
rubygems_version: 2.
|
145
|
+
rubygems_version: 2.6.10
|
142
146
|
signing_key:
|
143
147
|
specification_version: 4
|
144
148
|
summary: Ruby implementation of http://goessner.net/articles/JsonPath/
|