jsonpath 1.1.0 → 1.1.3

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