jsonpath 1.0.0 → 1.0.1
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/.rubocop_todo.yml +33 -18
- data/lib/jsonpath.rb +6 -4
- data/lib/jsonpath/enumerable.rb +4 -0
- data/lib/jsonpath/parser.rb +33 -32
- data/lib/jsonpath/version.rb +1 -1
- data/test/test_jsonpath.rb +23 -17
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d45aa52a5d4a9500f243abaa6ff69131a7e5a8653ffacd1ff0787946774d12f3
|
4
|
+
data.tar.gz: bd0903ab08210ac2e5a44a56f548104c1924f2d9a9db9d5006a51db86aafc648
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96a161911383f2c7870bd5450020aaaaceb1ea4305672f5a08493016e4fdcc5fb346001b3e8a377c16588c7bcd5adf9b0be1ef51d18b483404619324600501bb
|
7
|
+
data.tar.gz: 2c824f8916a742f0e5a9201c858c683bf2df7e9c7d5e38f7b7206fc8f511f69741afb3378993f3ed7e8f396148a14596ce7b9e78f0a739d2aeafc15591a0b585
|
data/.rubocop_todo.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on
|
3
|
+
# on 2019-01-25 09:23:04 +0100 using RuboCop version 0.63.1.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
@@ -18,31 +18,32 @@ Lint/IneffectiveAccessModifier:
|
|
18
18
|
Exclude:
|
19
19
|
- 'lib/jsonpath.rb'
|
20
20
|
|
21
|
-
# Offense count:
|
21
|
+
# Offense count: 17
|
22
22
|
Metrics/AbcSize:
|
23
|
-
Max:
|
23
|
+
Max: 60
|
24
24
|
|
25
25
|
# Offense count: 2
|
26
26
|
# Configuration parameters: CountComments, ExcludedMethods.
|
27
|
+
# ExcludedMethods: refine
|
27
28
|
Metrics/BlockLength:
|
28
|
-
Max:
|
29
|
+
Max: 37
|
29
30
|
|
30
31
|
# Offense count: 1
|
31
32
|
# Configuration parameters: CountBlocks.
|
32
33
|
Metrics/BlockNesting:
|
33
34
|
Max: 4
|
34
35
|
|
35
|
-
# Offense count:
|
36
|
+
# Offense count: 3
|
36
37
|
# Configuration parameters: CountComments.
|
37
38
|
Metrics/ClassLength:
|
38
|
-
Max:
|
39
|
+
Max: 739
|
39
40
|
|
40
|
-
# Offense count:
|
41
|
+
# Offense count: 7
|
41
42
|
Metrics/CyclomaticComplexity:
|
42
|
-
Max:
|
43
|
+
Max: 20
|
43
44
|
|
44
|
-
# Offense count:
|
45
|
-
# Configuration parameters: CountComments.
|
45
|
+
# Offense count: 26
|
46
|
+
# Configuration parameters: CountComments, ExcludedMethods.
|
46
47
|
Metrics/MethodLength:
|
47
48
|
Max: 52
|
48
49
|
|
@@ -53,9 +54,16 @@ Metrics/ParameterLists:
|
|
53
54
|
|
54
55
|
# Offense count: 6
|
55
56
|
Metrics/PerceivedComplexity:
|
56
|
-
Max:
|
57
|
+
Max: 21
|
57
58
|
|
58
59
|
# Offense count: 1
|
60
|
+
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
|
61
|
+
# AllowedNames: io, id, to, by, on, in, at, ip, db
|
62
|
+
Naming/UncommunicativeMethodParamName:
|
63
|
+
Exclude:
|
64
|
+
- 'lib/jsonpath/parser.rb'
|
65
|
+
|
66
|
+
# Offense count: 15
|
59
67
|
# Configuration parameters: AllowedChars.
|
60
68
|
Style/AsciiComments:
|
61
69
|
Exclude:
|
@@ -69,11 +77,12 @@ Style/Documentation:
|
|
69
77
|
- 'lib/jsonpath/enumerable.rb'
|
70
78
|
- 'lib/jsonpath/proxy.rb'
|
71
79
|
|
72
|
-
# Offense count:
|
80
|
+
# Offense count: 3
|
73
81
|
# Configuration parameters: MinBodyLength.
|
74
82
|
Style/GuardClause:
|
75
83
|
Exclude:
|
76
84
|
- 'lib/jsonpath/enumerable.rb'
|
85
|
+
- 'lib/jsonpath/parser.rb'
|
77
86
|
|
78
87
|
# Offense count: 2
|
79
88
|
# Cop supports --auto-correct.
|
@@ -81,16 +90,22 @@ Style/IfUnlessModifier:
|
|
81
90
|
Exclude:
|
82
91
|
- 'lib/jsonpath/enumerable.rb'
|
83
92
|
|
84
|
-
# Offense count:
|
93
|
+
# Offense count: 1
|
94
|
+
Style/MultipleComparison:
|
95
|
+
Exclude:
|
96
|
+
- 'lib/jsonpath/parser.rb'
|
97
|
+
|
98
|
+
# Offense count: 3
|
85
99
|
# Cop supports --auto-correct.
|
86
|
-
# Configuration parameters: AutoCorrect, EnforcedStyle.
|
100
|
+
# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods.
|
87
101
|
# SupportedStyles: predicate, comparison
|
88
102
|
Style/NumericPredicate:
|
89
103
|
Exclude:
|
90
104
|
- 'spec/**/*'
|
91
105
|
- 'lib/jsonpath/enumerable.rb'
|
106
|
+
- 'lib/jsonpath/parser.rb'
|
92
107
|
|
93
|
-
# Offense count:
|
108
|
+
# Offense count: 3
|
94
109
|
# Cop supports --auto-correct.
|
95
110
|
# Configuration parameters: EnforcedStyle, AllowInnerSlashes.
|
96
111
|
# SupportedStyles: slashes, percent_r, mixed
|
@@ -98,15 +113,15 @@ Style/RegexpLiteral:
|
|
98
113
|
Exclude:
|
99
114
|
- 'lib/jsonpath/parser.rb'
|
100
115
|
|
101
|
-
# Offense count:
|
116
|
+
# Offense count: 4
|
102
117
|
# Cop supports --auto-correct.
|
103
118
|
Style/RescueModifier:
|
104
119
|
Exclude:
|
105
120
|
- 'lib/jsonpath/enumerable.rb'
|
106
121
|
- 'lib/jsonpath/parser.rb'
|
107
122
|
|
108
|
-
# Offense count:
|
123
|
+
# Offense count: 89
|
109
124
|
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
110
125
|
# URISchemes: http, https
|
111
126
|
Metrics/LineLength:
|
112
|
-
Max:
|
127
|
+
Max: 296
|
data/lib/jsonpath.rb
CHANGED
@@ -33,10 +33,12 @@ class JsonPath
|
|
33
33
|
nil
|
34
34
|
elsif token = scanner.scan(/[><=] \d+/)
|
35
35
|
@path.last << token
|
36
|
-
# TODO: If there are characters that it can't match in the previous legs, this will throw
|
37
|
-
# a RuntimeError: can't modify frozen String error.
|
38
36
|
elsif token = scanner.scan(/./)
|
39
|
-
|
37
|
+
begin
|
38
|
+
@path.last << token
|
39
|
+
rescue RuntimeError
|
40
|
+
raise ArgumentError, "character '#{token}' not supported in query"
|
41
|
+
end
|
40
42
|
end
|
41
43
|
end
|
42
44
|
end
|
@@ -69,7 +71,7 @@ class JsonPath
|
|
69
71
|
a = enum_on(obj_or_str).to_a
|
70
72
|
if opts[:symbolize_keys]
|
71
73
|
a.map! do |e|
|
72
|
-
e.
|
74
|
+
e.each_with_object({}) { |(k, v), memo| memo[k.to_sym] = v; }
|
73
75
|
end
|
74
76
|
end
|
75
77
|
a
|
data/lib/jsonpath/enumerable.rb
CHANGED
@@ -15,6 +15,7 @@ class JsonPath
|
|
15
15
|
node = key ? context[key] : context
|
16
16
|
@_current_node = node
|
17
17
|
return yield_value(blk, context, key) if pos == @path.size
|
18
|
+
|
18
19
|
case expr = @path[pos]
|
19
20
|
when '*', '..', '@'
|
20
21
|
each(context, key, pos + 1, &blk)
|
@@ -47,6 +48,7 @@ class JsonPath
|
|
47
48
|
handle_question_mark(sub_path, node, pos, &blk)
|
48
49
|
else
|
49
50
|
next if node.is_a?(Array) && node.empty?
|
51
|
+
|
50
52
|
array_args = sub_path.split(':')
|
51
53
|
if array_args[0] == '*'
|
52
54
|
start_idx = 0
|
@@ -58,6 +60,7 @@ class JsonPath
|
|
58
60
|
else
|
59
61
|
start_idx = process_function_or_literal(array_args[0], 0)
|
60
62
|
next unless start_idx
|
63
|
+
|
61
64
|
end_idx = array_args[1] && ensure_exclusive_end_index(process_function_or_literal(array_args[1], -1)) || -1
|
62
65
|
next unless end_idx
|
63
66
|
next if start_idx == end_idx && start_idx >= node.size
|
@@ -66,6 +69,7 @@ class JsonPath
|
|
66
69
|
end_idx %= node.size
|
67
70
|
step = process_function_or_literal(array_args[2], 1)
|
68
71
|
next unless step
|
72
|
+
|
69
73
|
if @mode == :delete
|
70
74
|
(start_idx..end_idx).step(step) { |i| node[i] = nil }
|
71
75
|
node.compact!
|
data/lib/jsonpath/parser.rb
CHANGED
@@ -14,39 +14,39 @@ class JsonPath
|
|
14
14
|
# parse will parse an expression in the following way.
|
15
15
|
# Split the expression up into an array of legs for && and || operators.
|
16
16
|
# Parse this array into a map for which the keys are the parsed legs
|
17
|
-
#
|
17
|
+
# of the split. This map is then used to replace the expression with their
|
18
18
|
# corresponding boolean or numeric value. This might look something like this:
|
19
19
|
# ((false || false) && (false || true))
|
20
|
-
#
|
21
|
-
#
|
20
|
+
# Once this string is assembled... we proceed to evaluate from left to right.
|
21
|
+
# The above string is broken down like this:
|
22
22
|
# (false && (false || true))
|
23
23
|
# (false && true)
|
24
|
-
#
|
24
|
+
# false
|
25
25
|
def parse(exp)
|
26
26
|
exps = exp.split(/(&&)|(\|\|)/)
|
27
27
|
construct_expression_map(exps)
|
28
|
-
@_expr_map.each {|k, v| exp.sub!(k,
|
28
|
+
@_expr_map.each { |k, v| exp.sub!(k, v.to_s) }
|
29
29
|
raise ArgumentError, "unmatched parenthesis in expression: #{exp}" unless check_parenthesis_count(exp)
|
30
|
-
|
31
|
-
|
32
|
-
end
|
30
|
+
|
31
|
+
exp = parse_parentheses(exp) while exp.include?('(')
|
33
32
|
bool_or_exp(exp)
|
34
33
|
end
|
35
34
|
|
36
35
|
# Construct a map for which the keys are the expressions
|
37
|
-
#
|
36
|
+
# and the values are the corresponding parsed results.
|
38
37
|
# Exp.:
|
39
38
|
# {"(@['author'] =~ /herman|lukyanenko/i)"=>0}
|
40
39
|
# {"@['isTrue']"=>true}
|
41
40
|
def construct_expression_map(exps)
|
42
|
-
exps.each_with_index do |item,
|
41
|
+
exps.each_with_index do |item, _index|
|
43
42
|
next if item == '&&' || item == '||'
|
43
|
+
|
44
44
|
item = item.strip.gsub(/\)*$/, '').gsub(/^\(*/, '')
|
45
45
|
@_expr_map[item] = parse_exp(item)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
#
|
49
|
+
# using a scanner break down the individual expressions and determine if
|
50
50
|
# there is a match in the JSON for it or not.
|
51
51
|
def parse_exp(exp)
|
52
52
|
exp = exp.sub(/@/, '').gsub(/^\(/, '').gsub(/\)$/, '').tr('"', '\'').strip
|
@@ -100,34 +100,33 @@ class JsonPath
|
|
100
100
|
return nil unless hash.is_a? Hash
|
101
101
|
return nil unless hash.key?(keys.first)
|
102
102
|
return hash.fetch(keys.first) if keys.size == 1
|
103
|
+
|
103
104
|
prev = keys.shift
|
104
105
|
dig(keys, hash.fetch(prev))
|
105
106
|
end
|
106
107
|
|
107
|
-
#
|
108
|
-
#
|
108
|
+
# This will break down a parenthesis from the left to the right
|
109
|
+
# and replace the given expression with it's returned value.
|
109
110
|
# It does this in order to make it easy to eliminate groups
|
110
111
|
# one-by-one.
|
111
112
|
def parse_parentheses(str)
|
112
113
|
opening_index = 0
|
113
114
|
closing_index = 0
|
114
115
|
|
115
|
-
(0..str.length-1).step(1) do |i|
|
116
|
-
if str[i] == '('
|
117
|
-
opening_index = i
|
118
|
-
end
|
116
|
+
(0..str.length - 1).step(1) do |i|
|
117
|
+
opening_index = i if str[i] == '('
|
119
118
|
if str[i] == ')'
|
120
119
|
closing_index = i
|
121
120
|
break
|
122
121
|
end
|
123
122
|
end
|
124
123
|
|
125
|
-
to_parse = str[opening_index+1..closing_index-1]
|
124
|
+
to_parse = str[opening_index + 1..closing_index - 1]
|
126
125
|
|
127
|
-
#
|
126
|
+
# handle cases like (true && true || false && true) in
|
128
127
|
# one giant parenthesis.
|
129
128
|
top = to_parse.split(/(&&)|(\|\|)/)
|
130
|
-
top = top.map
|
129
|
+
top = top.map(&:strip)
|
131
130
|
res = bool_or_exp(top.shift)
|
132
131
|
top.each_with_index do |item, index|
|
133
132
|
case item
|
@@ -138,42 +137,44 @@ class JsonPath
|
|
138
137
|
end
|
139
138
|
end
|
140
139
|
|
141
|
-
#
|
140
|
+
# if we are at the last item, the opening index will be 0
|
142
141
|
# and the closing index will be the last index. To avoid
|
143
142
|
# off-by-one errors we simply return the result at that point.
|
144
|
-
if closing_index+1 >= str.length && opening_index == 0
|
145
|
-
return
|
143
|
+
if closing_index + 1 >= str.length && opening_index == 0
|
144
|
+
return res.to_s
|
146
145
|
else
|
147
|
-
return "#{str[0..opening_index-1]}#{res}#{str[closing_index+1..str.length]}"
|
146
|
+
return "#{str[0..opening_index - 1]}#{res}#{str[closing_index + 1..str.length]}"
|
148
147
|
end
|
149
148
|
end
|
150
149
|
|
151
|
-
#
|
152
|
-
#
|
150
|
+
# This is convoluted and I should probably refactor it somehow.
|
151
|
+
# The map that is created will contain strings since essentially I'm
|
153
152
|
# constructing a string like `true || true && false`.
|
154
153
|
# With eval the need for this would disappear but never the less, here
|
155
|
-
#
|
154
|
+
# it is. The fact is that the results can be either boolean, or a number
|
156
155
|
# in case there is only indexing happening like give me the 3rd item... or
|
157
156
|
# it also can be nil in case of regexes or things that aren't found.
|
158
157
|
# Hence, I have to be clever here to see what kind of variable I need to
|
159
158
|
# provide back.
|
160
159
|
def bool_or_exp(b)
|
161
|
-
if
|
160
|
+
if b.to_s == 'true'
|
162
161
|
return true
|
163
|
-
elsif
|
162
|
+
elsif b.to_s == 'false'
|
164
163
|
return false
|
165
|
-
elsif
|
164
|
+
elsif b.to_s == ''
|
166
165
|
return nil
|
167
166
|
end
|
167
|
+
|
168
168
|
b = Float(b) rescue b
|
169
169
|
b
|
170
170
|
end
|
171
171
|
|
172
172
|
# this simply makes sure that we aren't getting into the whole
|
173
|
-
#
|
173
|
+
# parenthesis parsing business without knowing that every parenthesis
|
174
174
|
# has its pair.
|
175
175
|
def check_parenthesis_count(exp)
|
176
|
-
return true unless exp.include?(
|
176
|
+
return true unless exp.include?('(')
|
177
|
+
|
177
178
|
depth = 0
|
178
179
|
exp.chars.each do |c|
|
179
180
|
if c == '('
|
data/lib/jsonpath/version.rb
CHANGED
data/test/test_jsonpath.rb
CHANGED
@@ -228,18 +228,15 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
228
228
|
'delete_me' => [
|
229
229
|
'no' => 'do not'
|
230
230
|
]
|
231
|
-
}
|
232
|
-
},
|
231
|
+
} },
|
233
232
|
{ 'category' => 'fiction',
|
234
233
|
'author' => 'Evelyn Waugh',
|
235
234
|
'title' => 'Sword of Honour',
|
236
|
-
'price' => 13
|
237
|
-
},
|
235
|
+
'price' => 13 },
|
238
236
|
{ 'category' => 'fiction',
|
239
237
|
'author' => 'Aasdf',
|
240
238
|
'title' => 'Aaasdf2',
|
241
|
-
'price' => 1
|
242
|
-
}
|
239
|
+
'price' => 1 }
|
243
240
|
]
|
244
241
|
} }
|
245
242
|
json_deleted = { 'store' => {
|
@@ -249,18 +246,15 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
249
246
|
'title' => 'Sayings of the Century',
|
250
247
|
'price' => 9,
|
251
248
|
'tags' => %w[asdf asdf2],
|
252
|
-
'this' => {}
|
253
|
-
},
|
249
|
+
'this' => {} },
|
254
250
|
{ 'category' => 'fiction',
|
255
251
|
'author' => 'Evelyn Waugh',
|
256
252
|
'title' => 'Sword of Honour',
|
257
|
-
'price' => 13
|
258
|
-
},
|
253
|
+
'price' => 13 },
|
259
254
|
{ 'category' => 'fiction',
|
260
255
|
'author' => 'Aasdf',
|
261
256
|
'title' => 'Aaasdf2',
|
262
|
-
'price' => 1
|
263
|
-
}
|
257
|
+
'price' => 1 }
|
264
258
|
]
|
265
259
|
} }
|
266
260
|
assert_equal(json_deleted, JsonPath.for(json).delete('$..store.book..delete_me').obj)
|
@@ -605,19 +599,19 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
605
599
|
json = {
|
606
600
|
channels: [
|
607
601
|
{
|
608
|
-
name: "King's Speech"
|
602
|
+
name: "King's Speech"
|
609
603
|
}
|
610
604
|
]
|
611
605
|
}.to_json
|
612
606
|
|
613
|
-
assert_equal [{
|
607
|
+
assert_equal [{ 'name' => "King\'s Speech" }], JsonPath.on(json, "$..channels[?(@.name == 'King\'s Speech')]")
|
614
608
|
end
|
615
609
|
|
616
610
|
def test_curly_brackets
|
617
611
|
data = {
|
618
612
|
'{data}' => 'data'
|
619
613
|
}
|
620
|
-
assert_equal ['data'], JsonPath.new(
|
614
|
+
assert_equal ['data'], JsonPath.new('$.{data}').on(data)
|
621
615
|
end
|
622
616
|
|
623
617
|
def test_symbolize
|
@@ -660,7 +654,7 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
660
654
|
}
|
661
655
|
}
|
662
656
|
'
|
663
|
-
assert_equal [{:
|
657
|
+
assert_equal [{ price: 8.95, category: 'reference', title: 'Sayings of the Century', author: 'Nigel Rees' }, { price: 8.99, category: 'fiction', isbn: '0-553-21311-3', title: 'Moby Dick', author: 'Herman Melville', color: 'blue' }], JsonPath.new('$..book[::2]').on(data, symbolize_keys: true)
|
664
658
|
end
|
665
659
|
|
666
660
|
def test_changed
|
@@ -724,10 +718,22 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
724
718
|
|
725
719
|
def test_complex_nested_grouping_unmatched_parent
|
726
720
|
path = "$..book[?((@['author'] == 'Evelyn Waugh' || @['author'] == 'Herman Melville' && (@['price'] == 33 || @['price'] == 9))]"
|
727
|
-
err = assert_raises(ArgumentError,
|
721
|
+
err = assert_raises(ArgumentError, 'should have raised an exception') { JsonPath.new(path).on(@object) }
|
728
722
|
assert_match(/unmatched parenthesis in expression: \(\(false \|\| false && \(false \|\| true\)\)/, err.message)
|
729
723
|
end
|
730
724
|
|
725
|
+
def test_runtime_error_frozen_string
|
726
|
+
skip('in ruby version below 2.2.0 this error is not raised') if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.2.0')
|
727
|
+
json = '
|
728
|
+
{
|
729
|
+
"test": "something"
|
730
|
+
}
|
731
|
+
'.to_json
|
732
|
+
assert_raises(ArgumentError, "RuntimeError: character '|' not supported in query") do
|
733
|
+
JsonPath.on(json, '$.description|title')
|
734
|
+
end
|
735
|
+
end
|
736
|
+
|
731
737
|
def test_delete_more_items
|
732
738
|
a = { 'itemList' =>
|
733
739
|
[{ 'alfa' => 'beta1' },
|
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: 1.0.
|
4
|
+
version: 1.0.1
|
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: 2019-01-
|
12
|
+
date: 2019-01-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: multi_json
|
@@ -159,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
159
159
|
version: '0'
|
160
160
|
requirements: []
|
161
161
|
rubyforge_project: jsonpath
|
162
|
-
rubygems_version: 2.
|
162
|
+
rubygems_version: 2.7.8
|
163
163
|
signing_key:
|
164
164
|
specification_version: 4
|
165
165
|
summary: Ruby implementation of http://goessner.net/articles/JsonPath/
|