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