jsonpath 1.1.2 → 1.1.5
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/.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/
|