jsonpath 1.1.0 → 1.1.3

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: 27b1b5e54ebef03b9cde7f447c1dc6fb8e411b4a28f5039426c87782b250ab1a
4
- data.tar.gz: ae08f25885125bde4f301d905b9c6446bf0e922516aabce101a4e90088fd6e68
3
+ metadata.gz: 89da09f37794d1cf177f887ea0a6c7d0da21d7106f8fd92d74d0615148feca02
4
+ data.tar.gz: 116f72d5493e540e5fde487dac9e57c3ff7f4c5122b66997a09eea899cda59a7
5
5
  SHA512:
6
- metadata.gz: e4e778c37086407f3fb101b23c7f0507c48f0714ad2ae671d14ce52a2397e18f0f7433b8fa0fe651e503285002b4db1b70dad91ae4de1cf51b948f9ec736818b
7
- data.tar.gz: b0dac82fcb16862190ae1405680d4779d6b3208d6ad31e25e5b0eb800877ca8bf56bae2789bac11452dfd872593071334205208bcb3a7320e3a8c0b94ba206c9
6
+ metadata.gz: 25d571cacbb85435c838c25b5d3c9cd8d6d1c11b743927531af80cdfe45d4670e6e37b21030608cf2b2ff4c04c78033caeb492ceb76db24e5b35d9a514b9e669
7
+ data.tar.gz: 1b0fa8cdc648f6d889f00cd751e3e03e7f32693a5897238584d0ee67e3e3542117901755393bb36e513be6addc2fd4ab3223782df005f20024763cc14e0fc350
@@ -0,0 +1,31 @@
1
+ name: test
2
+ on:
3
+ - push
4
+ - pull_request
5
+ jobs:
6
+ test:
7
+ strategy:
8
+ fail-fast: false
9
+ matrix:
10
+ ruby-version:
11
+ - '2.5'
12
+ - '2.6'
13
+ - '2.7'
14
+ - ruby-head
15
+ - jruby-head
16
+ - truffleruby-head
17
+ runs-on:
18
+ - ubuntu-latest
19
+
20
+ runs-on: ${{ matrix.runs-on }}
21
+
22
+ steps:
23
+
24
+ - uses: actions/checkout@v2
25
+
26
+ - uses: ruby/setup-ruby@v1
27
+ with:
28
+ ruby-version: ${{ matrix.ruby-version }}
29
+ bundler-cache: true
30
+
31
+ - run: bundle exec rake test
data/Gemfile CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  source 'http://rubygems.org'
4
4
  gemspec
5
- gem 'rubocop', require: true, group: :test
5
+ # gem 'rubocop', require: true, group: :test
6
6
  gem 'simplecov', require: false, group: :test
data/README.md CHANGED
@@ -253,6 +253,42 @@ o = JsonPath.for(json).
253
253
  # => {"candy" => "big turks"}
254
254
  ```
255
255
 
256
+ ### Fetch all paths
257
+
258
+ To fetch all possible paths in given json, you can use `fetch_all_path`` method.
259
+
260
+ data:
261
+
262
+ ```bash
263
+ {
264
+ "store": {
265
+ "book": [
266
+ {
267
+ "category": "reference",
268
+ "author": "Nigel Rees"
269
+ },
270
+ {
271
+ "category": "fiction",
272
+ "author": "Evelyn Waugh"
273
+ }
274
+ ]
275
+ }
276
+ ```
277
+
278
+ ... and this query:
279
+
280
+ ```ruby
281
+ JsonPath.fetch_all_path(data)
282
+ ```
283
+
284
+ ... the result will be:
285
+
286
+ ```bash
287
+ ["$", "$.store", "$.store.book", "$.store.book[0].category", "$.store.book[0].author", "$.store.book[0]", "$.store.book[1].category", "$.store.book[1].author", "$.store.book[1]"]
288
+ ```
289
+
290
+
291
+
256
292
  # Contributions
257
293
 
258
294
  Please feel free to submit an Issue or a Pull Request any time you feel like
data/jsonpath.gemspec CHANGED
@@ -23,5 +23,6 @@ Gem::Specification.new do |s|
23
23
  s.add_development_dependency 'code_stats'
24
24
  s.add_development_dependency 'minitest', '~> 2.2.0'
25
25
  s.add_development_dependency 'phocus'
26
+ s.add_development_dependency 'racc'
26
27
  s.add_development_dependency 'rake'
27
28
  end
@@ -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)
@@ -151,9 +157,6 @@ class JsonPath
151
157
  identifiers = /@?((?<!\d)\.(?!\d)(\w+))+/.match(exp)
152
158
  if !identifiers.nil? && !@_current_node.methods.include?(identifiers[2].to_sym)
153
159
  exp_to_eval = exp.dup
154
- exp_to_eval[identifiers[0]] = identifiers[0].split('.').map do |el|
155
- el == '@' ? '@' : "['#{el}']"
156
- end.join
157
160
  begin
158
161
  return JsonPath::Parser.new(@_current_node, @options).parse(exp_to_eval)
159
162
  rescue StandardError
@@ -68,7 +68,7 @@ class JsonPath
68
68
  scanner = StringScanner.new(exp)
69
69
  elements = []
70
70
  until scanner.eos?
71
- if (t = scanner.scan(/\['[a-zA-Z@&*\/$%^?_]+'\]|\.[a-zA-Z0-9_]+[?!]?/))
71
+ if (t = scanner.scan(/\['[a-zA-Z@&*\/$%^?_]+'\]|\.[a-zA-Z0-9_]+[?]?/))
72
72
  elements << t.gsub(/[\[\]'.]|\s+/, '')
73
73
  elsif (t = scanner.scan(/(\s+)?[<>=!\-+][=~]?(\s+)?/))
74
74
  operator = t
@@ -159,9 +159,11 @@ class JsonPath
159
159
  res = bool_or_exp(top.shift)
160
160
  top.each_with_index do |item, index|
161
161
  if item == '&&'
162
- res &&= top[index + 1]
162
+ next_value = bool_or_exp(top[index + 1])
163
+ res &&= next_value
163
164
  elsif item == '||'
164
- res ||= top[index + 1]
165
+ next_value = bool_or_exp(top[index + 1])
166
+ res ||= next_value
165
167
  end
166
168
  end
167
169
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class JsonPath
4
- VERSION = '1.1.0'
4
+ VERSION = '1.1.3'
5
5
  end
data/lib/jsonpath.rb CHANGED
@@ -17,7 +17,8 @@ class JsonPath
17
17
  :default_path_leaf_to_null => false,
18
18
  :symbolize_keys => false,
19
19
  :use_symbols => false,
20
- :allow_send => true
20
+ :allow_send => true,
21
+ :max_nesting => 100
21
22
  }
22
23
 
23
24
  attr_accessor :path
@@ -44,11 +45,9 @@ class JsonPath
44
45
  elsif (token = scanner.scan(/[><=] \d+/))
45
46
  @path.last << token
46
47
  elsif (token = scanner.scan(/./))
47
- begin
48
- @path.last << token
49
- rescue RuntimeError
50
- raise ArgumentError, "character '#{token}' not supported in query"
51
- end
48
+ @path.last << token
49
+ else
50
+ raise ArgumentError, "character '#{scanner.peek(1)}' not supported in query"
52
51
  end
53
52
  end
54
53
  end
@@ -86,13 +85,46 @@ class JsonPath
86
85
  end
87
86
  a
88
87
  end
88
+
89
+ def self.fetch_all_path(obj)
90
+ all_paths = ['$']
91
+ find_path(obj, '$', all_paths, obj.class == Array)
92
+ return all_paths
93
+ end
94
+
95
+ def self.find_path(obj, root_key, all_paths, is_array = false)
96
+ obj.each do |key, value|
97
+ table_params = { key: key, root_key: root_key}
98
+ is_loop = value.class == Array || value.class == Hash
99
+ if is_loop
100
+ path_exp = construct_path(table_params)
101
+ all_paths << path_exp
102
+ find_path(value, path_exp, all_paths, value.class == Array)
103
+ elsif is_array
104
+ table_params[:index] = obj.find_index(key)
105
+ path_exp = construct_path(table_params)
106
+ find_path(key, path_exp, all_paths, key.class == Array) if key.class == Hash || key.class == Array
107
+ all_paths << path_exp
108
+ else
109
+ all_paths << construct_path(table_params)
110
+ end
111
+ end
112
+ end
113
+
114
+ def self.construct_path(table_row)
115
+ if table_row[:index]
116
+ return table_row[:root_key] + '['+ table_row[:index].to_s + ']'
117
+ else
118
+ return table_row[:root_key] + '.'+ table_row[:key]
119
+ end
120
+ end
89
121
 
90
122
  def first(obj_or_str, *args)
91
123
  enum_on(obj_or_str).first(*args)
92
124
  end
93
125
 
94
126
  def enum_on(obj_or_str, mode = nil)
95
- JsonPath::Enumerable.new(self, self.class.process_object(obj_or_str), mode,
127
+ JsonPath::Enumerable.new(self, self.class.process_object(obj_or_str, @opts), mode,
96
128
  @opts)
97
129
  end
98
130
  alias [] enum_on
@@ -107,8 +139,8 @@ class JsonPath
107
139
 
108
140
  private
109
141
 
110
- def self.process_object(obj_or_str)
111
- obj_or_str.is_a?(String) ? MultiJson.decode(obj_or_str) : obj_or_str
142
+ def self.process_object(obj_or_str, opts = {})
143
+ obj_or_str.is_a?(String) ? MultiJson.decode(obj_or_str, max_nesting: opts[:max_nesting]) : obj_or_str
112
144
  end
113
145
 
114
146
  def deep_clone
@@ -70,24 +70,60 @@ class TestJsonpath < MiniTest::Unit::TestCase
70
70
  assert_equal [@object['store']['book'][0], @object['store']['book'][2]], JsonPath.new("$..book[?(@['price'] < 10)]").on(@object)
71
71
  assert_equal [@object['store']['book'][0], @object['store']['book'][2]], JsonPath.new("$..book[?(@['price'] == 9)]").on(@object)
72
72
  assert_equal [@object['store']['book'][3]], JsonPath.new("$..book[?(@['price'] > 20)]").on(@object)
73
- assert_equal [
74
- @object['store']['book'][0],
75
- @object['store']['book'][4],
76
- @object['store']['book'][5],
77
- @object['store']['book'][6]
78
- ], JsonPath.new("$..book[?(@['category'] != 'fiction')]").on(@object)
73
+ end
74
+
75
+ def test_not_equals_operator
76
+ expected =
77
+ [
78
+ @object['store']['book'][0],
79
+ @object['store']['book'][4],
80
+ @object['store']['book'][5],
81
+ @object['store']['book'][6]
82
+ ]
83
+ assert_equal(expected, JsonPath.new("$..book[?(@['category'] != 'fiction')]").on(@object))
84
+ assert_equal(expected, JsonPath.new("$..book[?(@['category']!=fiction)]").on(@object))
85
+ assert_equal(expected, JsonPath.new("$..book[?(@.category!=fiction)]").on(@object))
86
+ assert_equal(expected, JsonPath.new("$..book[?(@.category != 'fiction')]").on(@object))
79
87
  end
80
88
 
81
89
  def test_or_operator
82
90
  assert_equal [@object['store']['book'][1], @object['store']['book'][3]], JsonPath.new("$..book[?(@['price'] == 13 || @['price'] == 23)]").on(@object)
91
+ result = ["Sayings of the Century", "Sword of Honour", "Moby Dick", "The Lord of the Rings"]
92
+ assert_equal result, JsonPath.new("$..book[?(@.price==13 || @.price==9 || @.price==23)].title").on(@object)
93
+ assert_equal result, JsonPath.new("$..book[?(@.price==9 || @.price==23 || @.price==13)].title").on(@object)
94
+ assert_equal result, JsonPath.new("$..book[?(@.price==23 || @.price==13 || @.price==9)].title").on(@object)
95
+ end
96
+
97
+ def test_or_operator_with_not_equals
98
+ # Should be the same regardless of key style ( @.key vs @['key'] )
99
+ result = ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien', 'Lukyanenko']
100
+ assert_equal result, JsonPath.new("$..book[?(@['title']=='Osennie Vizity' || @['author']!='Lukyanenko')].author").on(@object)
101
+ assert_equal result, JsonPath.new("$..book[?(@.title=='Osennie Vizity' || @.author != Lukyanenko )].author").on(@object)
102
+ assert_equal result, JsonPath.new("$..book[?(@.title=='Osennie Vizity' || @.author!=Lukyanenko )].author").on(@object)
83
103
  end
84
104
 
85
105
  def test_and_operator
86
106
  assert_equal [], JsonPath.new("$..book[?(@['price'] == 13 && @['price'] == 23)]").on(@object)
107
+ assert_equal [], JsonPath.new("$..book[?(@.price>=13 && @.category==fiction && @.title==no_match)]").on(@object)
108
+ assert_equal [], JsonPath.new("$..book[?(@.title==no_match && @.category==fiction && @.price==13)]").on(@object)
109
+ assert_equal [], JsonPath.new("$..book[?(@.price==13 && @.title==no_match && @.category==fiction)]").on(@object)
110
+ assert_equal [], JsonPath.new("$..book[?(@.price==13 && @.bad_key_name==true && @.category==fiction)]").on(@object)
111
+
112
+ expected = [@object['store']['book'][1]]
113
+ assert_equal expected, JsonPath.new("$..book[?(@['price'] < 23 && @['price'] > 9)]").on(@object)
114
+ assert_equal expected, JsonPath.new("$..book[?(@.price < 23 && @.price > 9)]").on(@object)
115
+
116
+ expected = ['Sword of Honour', 'The Lord of the Rings']
117
+ assert_equal expected, JsonPath.new("$..book[?(@.price>=13 && @.category==fiction)].title").on(@object)
118
+ assert_equal ['The Lord of the Rings'], JsonPath.new("$..book[?(@.category==fiction && @.isbn && @.price>9)].title").on(@object)
119
+ assert_equal ['Sayings of the Century'], JsonPath.new("$..book[?(@['price'] == 9 && @.author=='Nigel Rees')].title").on(@object)
120
+ assert_equal ['Sayings of the Century'], JsonPath.new("$..book[?(@['price'] == 9 && @.tags..asdf)].title").on(@object)
87
121
  end
88
122
 
89
- def test_and_operator_with_more_results
90
- assert_equal [@object['store']['book'][1]], JsonPath.new("$..book[?(@['price'] < 23 && @['price'] > 9)]").on(@object)
123
+ def test_and_operator_with_not_equals
124
+ expected = ['Nigel Rees']
125
+ assert_equal expected, JsonPath.new("$..book[?(@['price']==9 && @['category']!=fiction)].author").on(@object)
126
+ assert_equal expected, JsonPath.new("$..book[?(@.price==9 && @.category!=fiction)].author").on(@object)
91
127
  end
92
128
 
93
129
  def test_nested_grouping
@@ -570,11 +606,42 @@ class TestJsonpath < MiniTest::Unit::TestCase
570
606
  'name' => 'testname2'
571
607
  }]
572
608
  }
573
- assert_equal [{ 'isTrue' => true, 'name' => 'testname1' }], JsonPath.new('$.data[?(@.isTrue)]').on(data)
609
+
610
+ # These queries should be equivalent
611
+ expected = [{ 'isTrue' => true, 'name' => 'testname1' }]
612
+ assert_equal expected, JsonPath.new('$.data[?(@.isTrue)]').on(data)
613
+ assert_equal expected, JsonPath.new('$.data[?(@.isTrue==true)]').on(data)
614
+ assert_equal expected, JsonPath.new('$.data[?(@.isTrue == true)]').on(data)
615
+
616
+ # These queries should be equivalent
617
+ expected = [{ 'isTrue' => false, 'name' => 'testname2' }]
618
+ assert_equal expected, JsonPath.new('$.data[?(@.isTrue != true)]').on(data)
619
+ assert_equal expected, JsonPath.new('$.data[?(@.isTrue!=true)]').on(data)
620
+ assert_equal expected, JsonPath.new('$.data[?(@.isTrue==false)]').on(data)
621
+ end
622
+
623
+ def test_and_operator_with_boolean_parameter_value
624
+ data = {
625
+ 'data' => [{
626
+ 'hasProperty1' => true,
627
+ 'hasProperty2' => false,
628
+ 'name' => 'testname1'
629
+ }, {
630
+ 'hasProperty1' => false,
631
+ 'hasProperty2' => true,
632
+ 'name' => 'testname2'
633
+ }, {
634
+ 'hasProperty1' => true,
635
+ 'hasProperty2' => true,
636
+ 'name' => 'testname3'
637
+ }]
638
+ }
639
+ assert_equal ['testname3'], JsonPath.new('$.data[?(@.hasProperty1 && @.hasProperty2)].name').on(data)
574
640
  end
575
641
 
576
642
  def test_regex_simple
577
643
  assert_equal %w[asdf asdf2], JsonPath.new('$.store.book..tags[?(@ =~ /asdf/)]').on(@object)
644
+ assert_equal %w[asdf asdf2], JsonPath.new('$.store.book..tags[?(@=~/asdf/)]').on(@object)
578
645
  end
579
646
 
580
647
  def test_regex_simple_miss
@@ -1098,6 +1165,36 @@ class TestJsonpath < MiniTest::Unit::TestCase
1098
1165
  assert_equal [12, nil, nil], JsonPath.new('$.bars[*].foo', default_path_leaf_to_null: true).on(data)
1099
1166
  end
1100
1167
 
1168
+ def test_raise_max_nesting_error
1169
+ json = {
1170
+ a: {
1171
+ b: {
1172
+ c: {
1173
+ }
1174
+ }
1175
+ }
1176
+ }.to_json
1177
+
1178
+ assert_raises(MultiJson::ParseError) { JsonPath.new('$.a', max_nesting: 1).on(json) }
1179
+ end
1180
+
1181
+ def test_linefeed_in_path_error
1182
+ assert_raises(ArgumentError) { JsonPath.new("$.store\n.book") }
1183
+ end
1184
+
1185
+ def test_with_max_nesting_false
1186
+ json = {
1187
+ a: {
1188
+ b: {
1189
+ c: {
1190
+ }
1191
+ }
1192
+ }
1193
+ }.to_json
1194
+
1195
+ assert_equal [{}], JsonPath.new('$.a.b.c', max_nesting: false).on(json)
1196
+ end
1197
+
1101
1198
  def example_object
1102
1199
  { 'store' => {
1103
1200
  'book' => [
@@ -1152,4 +1249,26 @@ class TestJsonpath < MiniTest::Unit::TestCase
1152
1249
  '_links' => { 'self' => {} }
1153
1250
  } }
1154
1251
  end
1252
+
1253
+ def test_fetch_all_path
1254
+ data = {
1255
+ "foo" => nil,
1256
+ "bar" => {
1257
+ "baz" => nil
1258
+ },
1259
+ "bars" => [
1260
+ { "foo" => 12 },
1261
+ { "foo" => nil },
1262
+ { }
1263
+ ]
1264
+ }
1265
+ assert_equal ["$", "$.foo", "$.bar", "$.bar.baz", "$.bars", "$.bars[0].foo", "$.bars[0]", "$.bars[1].foo", "$.bars[1]", "$.bars[2]"], JsonPath.fetch_all_path(data)
1266
+ end
1267
+
1268
+
1269
+ def test_extractore_with_dollar_key
1270
+ json = {"test" => {"$" =>"success", "a" => "123"}}
1271
+ assert_equal ["success"], JsonPath.on(json, "$.test.$")
1272
+ assert_equal ["123"], JsonPath.on(json, "$.test.a")
1273
+ end
1155
1274
  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.0
4
+ version: 1.1.3
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: 2020-12-28 00:00:00.000000000 Z
12
+ date: 2023-05-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multi_json
@@ -81,6 +81,20 @@ dependencies:
81
81
  - - ">="
82
82
  - !ruby/object:Gem::Version
83
83
  version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: racc
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
84
98
  - !ruby/object:Gem::Dependency
85
99
  name: rake
86
100
  requirement: !ruby/object:Gem::Requirement
@@ -106,11 +120,11 @@ extra_rdoc_files:
106
120
  - README.md
107
121
  files:
108
122
  - ".gemtest"
123
+ - ".github/workflows/test.yml"
109
124
  - ".gitignore"
110
125
  - ".rspec"
111
126
  - ".rubocop.yml"
112
127
  - ".rubocop_todo.yml"
113
- - ".travis.yml"
114
128
  - Gemfile
115
129
  - LICENSE.md
116
130
  - README.md
@@ -145,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
159
  - !ruby/object:Gem::Version
146
160
  version: '0'
147
161
  requirements: []
148
- rubygems_version: 3.1.2
162
+ rubygems_version: 3.4.1
149
163
  signing_key:
150
164
  specification_version: 4
151
165
  summary: Ruby implementation of http://goessner.net/articles/JsonPath/
data/.travis.yml DELETED
@@ -1,8 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.5
4
- - 2.6
5
- - 2.7
6
- - ruby-head
7
- - jruby-head
8
- - truffleruby-head