jsonpath 1.1.2 → 1.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +3 -1
- data/README.md +1 -1
- data/jsonpath.gemspec +1 -1
- data/lib/jsonpath/enumerable.rb +12 -5
- data/lib/jsonpath/version.rb +1 -1
- data/lib/jsonpath.rb +18 -9
- data/test/test_jsonpath.rb +65 -7
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ffcdd9076a2bd58ead93c850c8a1ed842a202e6db62314fb2b6c8fd5f8650fb5
|
4
|
+
data.tar.gz: 3d6624bd18086aa892f93bff141dd7bf33e516e02c3fd7e8828db13eb3a84436
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 492ed8f729cfdb038c2b0303f49bf2bf1bfb85bd4a7d6ae2e37c2b559bf288b73110db65b2c1642f4e1954cde213d10488ab996ad9261c108f253f03aeb41d12
|
7
|
+
data.tar.gz: 2642b36da6a171649a9b0dc2e973994a7d3268e71a62ea290e75f91bd7f7af621b0b0dd6d9fb6a1f7148943f2c5ce6965f8de0db69db4041bc973576b7694425
|
data/.github/workflows/test.yml
CHANGED
data/README.md
CHANGED
data/jsonpath.gemspec
CHANGED
@@ -5,7 +5,7 @@ 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_ruby_version = '>= 2.
|
8
|
+
s.required_ruby_version = '>= 2.6'
|
9
9
|
s.authors = ['Joshua Hull', 'Gergely Brautigam']
|
10
10
|
s.summary = 'Ruby implementation of http://goessner.net/articles/JsonPath/'
|
11
11
|
s.description = 'Ruby implementation of http://goessner.net/articles/JsonPath/.'
|
data/lib/jsonpath/enumerable.rb
CHANGED
@@ -21,9 +21,13 @@ class JsonPath
|
|
21
21
|
when '*', '..', '@'
|
22
22
|
each(context, key, pos + 1, &blk)
|
23
23
|
when '$'
|
24
|
-
|
24
|
+
if node == @object
|
25
|
+
each(context, key, pos + 1, &blk)
|
26
|
+
else
|
27
|
+
handle_wildcard(node, "['#{expr}']", context, key, pos, &blk)
|
28
|
+
end
|
25
29
|
when /^\[(.*)\]$/
|
26
|
-
|
30
|
+
handle_wildcard(node, expr, context, key, pos, &blk)
|
27
31
|
when /\(.*\)/
|
28
32
|
keys = expr.gsub(/[()]/, '').split(',').map(&:strip)
|
29
33
|
new_context = filter_context(context, keys)
|
@@ -51,7 +55,7 @@ class JsonPath
|
|
51
55
|
end
|
52
56
|
end
|
53
57
|
|
54
|
-
def
|
58
|
+
def handle_wildcard(node, expr, _context, _key, pos, &blk)
|
55
59
|
expr[1, expr.size - 2].split(',').each do |sub_path|
|
56
60
|
case sub_path[0]
|
57
61
|
when '\'', '"'
|
@@ -64,6 +68,7 @@ class JsonPath
|
|
64
68
|
else
|
65
69
|
next if node.is_a?(Array) && node.empty?
|
66
70
|
next if node.nil? # when default_path_leaf_to_null is true
|
71
|
+
next if node.size.zero?
|
67
72
|
|
68
73
|
array_args = sub_path.split(':')
|
69
74
|
if array_args[0] == '*'
|
@@ -81,6 +86,7 @@ class JsonPath
|
|
81
86
|
next unless end_idx
|
82
87
|
next if start_idx == end_idx && start_idx >= node.size
|
83
88
|
end
|
89
|
+
|
84
90
|
start_idx %= node.size
|
85
91
|
end_idx %= node.size
|
86
92
|
step = process_function_or_literal(array_args[2], 1)
|
@@ -148,8 +154,9 @@ class JsonPath
|
|
148
154
|
return Integer(exp) if exp[0] != '('
|
149
155
|
return nil unless @_current_node
|
150
156
|
|
151
|
-
identifiers = /@?((?<!\d)\.(?!\d)(\w+))+/.match(exp)
|
152
|
-
|
157
|
+
identifiers = /@?(((?<!\d)\.(?!\d)(\w+))|\['(.*?)'\])+/.match(exp)
|
158
|
+
# to filter arrays with known/unknown name.
|
159
|
+
if (!identifiers.nil? && !(@_current_node.methods.include?(identifiers[2]&.to_sym) || @_current_node.methods.include?(identifiers[4]&.to_sym)))
|
153
160
|
exp_to_eval = exp.dup
|
154
161
|
begin
|
155
162
|
return JsonPath::Parser.new(@_current_node, @options).parse(exp_to_eval)
|
data/lib/jsonpath/version.rb
CHANGED
data/lib/jsonpath.rb
CHANGED
@@ -12,25 +12,27 @@ require 'jsonpath/parser'
|
|
12
12
|
# into a token array.
|
13
13
|
class JsonPath
|
14
14
|
PATH_ALL = '$..*'
|
15
|
+
MAX_NESTING_ALLOWED = 100
|
15
16
|
|
16
17
|
DEFAULT_OPTIONS = {
|
17
18
|
:default_path_leaf_to_null => false,
|
18
19
|
:symbolize_keys => false,
|
19
20
|
:use_symbols => false,
|
20
21
|
:allow_send => true,
|
21
|
-
:max_nesting =>
|
22
|
+
:max_nesting => MAX_NESTING_ALLOWED
|
22
23
|
}
|
23
24
|
|
24
25
|
attr_accessor :path
|
25
26
|
|
26
27
|
def initialize(path, opts = {})
|
27
28
|
@opts = DEFAULT_OPTIONS.merge(opts)
|
29
|
+
set_max_nesting
|
28
30
|
scanner = StringScanner.new(path.strip)
|
29
31
|
@path = []
|
30
32
|
until scanner.eos?
|
31
33
|
if (token = scanner.scan(/\$\B|@\B|\*|\.\./))
|
32
34
|
@path << token
|
33
|
-
elsif (token = scanner.scan(/[
|
35
|
+
elsif (token = scanner.scan(/[$@\p{Alnum}:{}_ -]+/))
|
34
36
|
@path << "['#{token}']"
|
35
37
|
elsif (token = scanner.scan(/'(.*?)'/))
|
36
38
|
@path << "[#{token}]"
|
@@ -45,11 +47,9 @@ class JsonPath
|
|
45
47
|
elsif (token = scanner.scan(/[><=] \d+/))
|
46
48
|
@path.last << token
|
47
49
|
elsif (token = scanner.scan(/./))
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
raise ArgumentError, "character '#{token}' not supported in query"
|
52
|
-
end
|
50
|
+
@path.last << token
|
51
|
+
else
|
52
|
+
raise ArgumentError, "character '#{scanner.peek(1)}' not supported in query"
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
@@ -80,14 +80,14 @@ class JsonPath
|
|
80
80
|
|
81
81
|
def on(obj_or_str, opts = {})
|
82
82
|
a = enum_on(obj_or_str).to_a
|
83
|
-
if opts
|
83
|
+
if symbolize_keys?(opts)
|
84
84
|
a.map! do |e|
|
85
85
|
e.each_with_object({}) { |(k, v), memo| memo[k.to_sym] = v; }
|
86
86
|
end
|
87
87
|
end
|
88
88
|
a
|
89
89
|
end
|
90
|
-
|
90
|
+
|
91
91
|
def self.fetch_all_path(obj)
|
92
92
|
all_paths = ['$']
|
93
93
|
find_path(obj, '$', all_paths, obj.class == Array)
|
@@ -148,4 +148,13 @@ class JsonPath
|
|
148
148
|
def deep_clone
|
149
149
|
Marshal.load Marshal.dump(self)
|
150
150
|
end
|
151
|
+
|
152
|
+
def set_max_nesting
|
153
|
+
return unless @opts[:max_nesting].is_a?(Integer) && @opts[:max_nesting] > MAX_NESTING_ALLOWED
|
154
|
+
@opts[:max_nesting] = false
|
155
|
+
end
|
156
|
+
|
157
|
+
def symbolize_keys?(opts)
|
158
|
+
opts.fetch(:symbolize_keys, @opts&.dig(:symbolize_keys))
|
159
|
+
end
|
151
160
|
end
|
data/test/test_jsonpath.rb
CHANGED
@@ -245,7 +245,7 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
245
245
|
end
|
246
246
|
|
247
247
|
def test_counting
|
248
|
-
assert_equal
|
248
|
+
assert_equal 59, JsonPath.new('$..*').on(@object).to_a.size
|
249
249
|
end
|
250
250
|
|
251
251
|
def test_space_in_path
|
@@ -475,6 +475,16 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
475
475
|
JsonPath.new('$.store._links').on(@object)
|
476
476
|
end
|
477
477
|
|
478
|
+
def test_support_for_umlauts_in_member_names
|
479
|
+
assert_equal [@object['store']['Übermorgen']],
|
480
|
+
JsonPath.new('$.store.Übermorgen').on(@object)
|
481
|
+
end
|
482
|
+
|
483
|
+
def test_support_for_spaces_in_member_name
|
484
|
+
assert_equal [@object['store']['Title Case']],
|
485
|
+
JsonPath.new('$.store.Title Case').on(@object)
|
486
|
+
end
|
487
|
+
|
478
488
|
def test_dig_return_string
|
479
489
|
assert_equal ['asdf'], JsonPath.new("$.store.book..tags[?(@ == 'asdf')]").on(@object)
|
480
490
|
assert_equal [], JsonPath.new("$.store.book..tags[?(@ == 'not_asdf')]").on(@object)
|
@@ -885,13 +895,17 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
885
895
|
path = "$..book[?((@['author'] == 'Evelyn Waugh' || @['author'] == 'Herman Melville') && (@['price'] == 33 || @['price'] == 9))]"
|
886
896
|
assert_equal [@object['store']['book'][2]], JsonPath.new(path).on(@object)
|
887
897
|
end
|
888
|
-
|
889
|
-
def
|
890
|
-
path = "$..
|
891
|
-
|
892
|
-
assert_match(/unmatched parenthesis in expression: \(\(false \|\| false && \(false \|\| true\)\)/, err.message)
|
898
|
+
|
899
|
+
def test_nested_with_unknown_key
|
900
|
+
path = "$..[?(@.price == 9 || @.price == 33)].title"
|
901
|
+
assert_equal ["Sayings of the Century", "Moby Dick", "Sayings of the Century", "Moby Dick"], JsonPath.new(path).on(@object)
|
893
902
|
end
|
894
903
|
|
904
|
+
def test_nested_with_unknown_key_filtered_array
|
905
|
+
path = "$..[?(@['price'] == 9 || @['price'] == 33)].title"
|
906
|
+
assert_equal ["Sayings of the Century", "Moby Dick", "Sayings of the Century", "Moby Dick"], JsonPath.new(path).on(@object)
|
907
|
+
end
|
908
|
+
|
895
909
|
def test_runtime_error_frozen_string
|
896
910
|
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') || Gem::Version.new(RUBY_VERSION) > Gem::Version::new('2.6')
|
897
911
|
json = '
|
@@ -1178,6 +1192,10 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
1178
1192
|
assert_raises(MultiJson::ParseError) { JsonPath.new('$.a', max_nesting: 1).on(json) }
|
1179
1193
|
end
|
1180
1194
|
|
1195
|
+
def test_linefeed_in_path_error
|
1196
|
+
assert_raises(ArgumentError) { JsonPath.new("$.store\n.book") }
|
1197
|
+
end
|
1198
|
+
|
1181
1199
|
def test_with_max_nesting_false
|
1182
1200
|
json = {
|
1183
1201
|
a: {
|
@@ -1191,6 +1209,31 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
1191
1209
|
assert_equal [{}], JsonPath.new('$.a.b.c', max_nesting: false).on(json)
|
1192
1210
|
end
|
1193
1211
|
|
1212
|
+
def test_initialize_with_max_nesting_exceeding_limit
|
1213
|
+
json = {
|
1214
|
+
a: {
|
1215
|
+
b: {
|
1216
|
+
c: {
|
1217
|
+
}
|
1218
|
+
}
|
1219
|
+
}
|
1220
|
+
}.to_json
|
1221
|
+
|
1222
|
+
json_obj = JsonPath.new('$.a.b.c', max_nesting: 105)
|
1223
|
+
assert_equal [{}], json_obj.on(json)
|
1224
|
+
assert_equal false, json_obj.instance_variable_get(:@opts)[:max_nesting]
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
def test_initialize_without_max_nesting_exceeding_limit
|
1228
|
+
json_obj = JsonPath.new('$.a.b.c', max_nesting: 90)
|
1229
|
+
assert_equal 90, json_obj.instance_variable_get(:@opts)[:max_nesting]
|
1230
|
+
end
|
1231
|
+
|
1232
|
+
def test_initialize_with_max_nesting_false_limit
|
1233
|
+
json_obj = JsonPath.new('$.a.b.c', max_nesting: false)
|
1234
|
+
assert_equal false, json_obj.instance_variable_get(:@opts)[:max_nesting]
|
1235
|
+
end
|
1236
|
+
|
1194
1237
|
def example_object
|
1195
1238
|
{ 'store' => {
|
1196
1239
|
'book' => [
|
@@ -1242,10 +1285,12 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
1242
1285
|
},
|
1243
1286
|
'@id' => 'http://example.org/store/42',
|
1244
1287
|
'$meta-data' => 'whatevs',
|
1288
|
+
'Übermorgen' => 'The day after tomorrow',
|
1289
|
+
'Title Case' => 'A title case string',
|
1245
1290
|
'_links' => { 'self' => {} }
|
1246
1291
|
} }
|
1247
1292
|
end
|
1248
|
-
|
1293
|
+
|
1249
1294
|
def test_fetch_all_path
|
1250
1295
|
data = {
|
1251
1296
|
"foo" => nil,
|
@@ -1260,4 +1305,17 @@ class TestJsonpath < MiniTest::Unit::TestCase
|
|
1260
1305
|
}
|
1261
1306
|
assert_equal ["$", "$.foo", "$.bar", "$.bar.baz", "$.bars", "$.bars[0].foo", "$.bars[0]", "$.bars[1].foo", "$.bars[1]", "$.bars[2]"], JsonPath.fetch_all_path(data)
|
1262
1307
|
end
|
1308
|
+
|
1309
|
+
|
1310
|
+
def test_extractore_with_dollar_key
|
1311
|
+
json = {"test" => {"$" =>"success", "a" => "123"}}
|
1312
|
+
assert_equal ["success"], JsonPath.on(json, "$.test.$")
|
1313
|
+
assert_equal ["123"], JsonPath.on(json, "$.test.a")
|
1314
|
+
end
|
1315
|
+
|
1316
|
+
def test_symbolize_key
|
1317
|
+
data = { "store" => { "book" => [{"category" => "reference"}]}}
|
1318
|
+
assert_equal [{"category": "reference"}], JsonPath.new('$..book[0]', symbolize_keys: true).on(data)
|
1319
|
+
assert_equal [{"category": "reference"}], JsonPath.new('$..book[0]').on(data, symbolize_keys: true)
|
1320
|
+
end
|
1263
1321
|
end
|
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.1.
|
4
|
+
version: 1.1.5
|
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:
|
12
|
+
date: 2023-10-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: multi_json
|
@@ -152,14 +152,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
152
152
|
requirements:
|
153
153
|
- - ">="
|
154
154
|
- !ruby/object:Gem::Version
|
155
|
-
version: '2.
|
155
|
+
version: '2.6'
|
156
156
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
157
157
|
requirements:
|
158
158
|
- - ">="
|
159
159
|
- !ruby/object:Gem::Version
|
160
160
|
version: '0'
|
161
161
|
requirements: []
|
162
|
-
rubygems_version: 3.
|
162
|
+
rubygems_version: 3.4.1
|
163
163
|
signing_key:
|
164
164
|
specification_version: 4
|
165
165
|
summary: Ruby implementation of http://goessner.net/articles/JsonPath/
|