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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab3e08744fe7f8ba3b64670fee8f75a0fa4c9b339de8257f64cdc7ea24f26965
4
- data.tar.gz: 447ce47a7c5df36c2fa290da5aafc77304d5e8db9318cfcac2b97dd9ae8c9c8c
3
+ metadata.gz: ffcdd9076a2bd58ead93c850c8a1ed842a202e6db62314fb2b6c8fd5f8650fb5
4
+ data.tar.gz: 3d6624bd18086aa892f93bff141dd7bf33e516e02c3fd7e8828db13eb3a84436
5
5
  SHA512:
6
- metadata.gz: 5b458262f098ceea595a91a981136141d0ed3f7ef9782d8a205f619be1bb60a7234af11229527ed9fc9ef3baafec323ad33850d87b4dc389ac482c14bb4aa05f
7
- data.tar.gz: d1756aa04d5e07de855439aa0a4895078ab5c3c2c90c4c9ec755ba1da2ffb3fe11e59d1819e03f6a82e8670fd810b1c895da150a9d24176ddf4a86a54705fcbf
6
+ metadata.gz: 492ed8f729cfdb038c2b0303f49bf2bf1bfb85bd4a7d6ae2e37c2b559bf288b73110db65b2c1642f4e1954cde213d10488ab996ad9261c108f253f03aeb41d12
7
+ data.tar.gz: 2642b36da6a171649a9b0dc2e973994a7d3268e71a62ea290e75f91bd7f7af621b0b0dd6d9fb6a1f7148943f2c5ce6965f8de0db69db4041bc973576b7694425
@@ -8,9 +8,11 @@ jobs:
8
8
  fail-fast: false
9
9
  matrix:
10
10
  ruby-version:
11
- - '2.5'
12
11
  - '2.6'
13
12
  - '2.7'
13
+ - '3.2'
14
+ - '3.1'
15
+ - '3.0'
14
16
  - ruby-head
15
17
  - jruby-head
16
18
  - truffleruby-head
data/README.md CHANGED
@@ -255,7 +255,7 @@ o = JsonPath.for(json).
255
255
 
256
256
  ### Fetch all paths
257
257
 
258
- To fetch all possible paths in given json, you can use `fetch_all_paths` method.
258
+ To fetch all possible paths in given json, you can use `fetch_all_path`` method.
259
259
 
260
260
  data:
261
261
 
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.5'
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/.'
@@ -21,9 +21,13 @@ class JsonPath
21
21
  when '*', '..', '@'
22
22
  each(context, key, pos + 1, &blk)
23
23
  when '$'
24
- each(context, key, pos + 1, &blk) if node == @object
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
- handle_wildecard(node, expr, context, key, pos, &blk)
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 handle_wildecard(node, expr, _context, _key, pos, &blk)
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
- if !identifiers.nil? && !@_current_node.methods.include?(identifiers[2].to_sym)
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonPath
4
- VERSION = '1.1.2'
4
+ VERSION = '1.1.5'
5
5
  end
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 => 100
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(/[$@a-zA-Z0-9:{}_-]+/))
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
- begin
49
- @path.last << token
50
- rescue RuntimeError
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[:symbolize_keys]
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
@@ -245,7 +245,7 @@ class TestJsonpath < MiniTest::Unit::TestCase
245
245
  end
246
246
 
247
247
  def test_counting
248
- assert_equal 57, JsonPath.new('$..*').on(@object).to_a.size
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 test_complex_nested_grouping_unmatched_parent
890
- path = "$..book[?((@['author'] == 'Evelyn Waugh' || @['author'] == 'Herman Melville' && (@['price'] == 33 || @['price'] == 9))]"
891
- err = assert_raises(ArgumentError, 'should have raised an exception') { JsonPath.new(path).on(@object) }
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.2
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: 2022-04-24 00:00:00.000000000 Z
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.5'
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.3.7
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/