janeway-jsonpath 0.3.0 → 0.4.0

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +201 -28
  3. data/bin/janeway +36 -9
  4. data/lib/janeway/ast/expression.rb +1 -2
  5. data/lib/janeway/enumerator.rb +43 -0
  6. data/lib/janeway/error.rb +6 -9
  7. data/lib/janeway/functions/length.rb +1 -1
  8. data/lib/janeway/interpreter.rb +124 -19
  9. data/lib/janeway/interpreters/array_slice_selector_deleter.rb +41 -0
  10. data/lib/janeway/interpreters/array_slice_selector_interpreter.rb +15 -10
  11. data/lib/janeway/interpreters/base.rb +26 -2
  12. data/lib/janeway/interpreters/binary_operator_interpreter.rb +10 -7
  13. data/lib/janeway/interpreters/child_segment_deleter.rb +19 -0
  14. data/lib/janeway/interpreters/child_segment_interpreter.rb +48 -8
  15. data/lib/janeway/interpreters/current_node_interpreter.rb +5 -3
  16. data/lib/janeway/interpreters/descendant_segment_interpreter.rb +13 -9
  17. data/lib/janeway/interpreters/filter_selector_deleter.rb +65 -0
  18. data/lib/janeway/interpreters/filter_selector_interpreter.rb +54 -14
  19. data/lib/janeway/interpreters/function_interpreter.rb +7 -5
  20. data/lib/janeway/interpreters/index_selector_deleter.rb +26 -0
  21. data/lib/janeway/interpreters/index_selector_interpreter.rb +3 -2
  22. data/lib/janeway/interpreters/name_selector_deleter.rb +27 -0
  23. data/lib/janeway/interpreters/name_selector_interpreter.rb +19 -4
  24. data/lib/janeway/interpreters/root_node_deleter.rb +34 -0
  25. data/lib/janeway/interpreters/root_node_interpreter.rb +4 -2
  26. data/lib/janeway/interpreters/tree_constructor.rb +20 -1
  27. data/lib/janeway/interpreters/unary_operator_interpreter.rb +2 -2
  28. data/lib/janeway/interpreters/wildcard_selector_deleter.rb +32 -0
  29. data/lib/janeway/interpreters/wildcard_selector_interpreter.rb +26 -11
  30. data/lib/janeway/interpreters/yielder.rb +50 -12
  31. data/lib/janeway/lexer.rb +1 -1
  32. data/lib/janeway/normalized_path.rb +66 -0
  33. data/lib/janeway/parser.rb +3 -3
  34. data/lib/janeway/query.rb +70 -0
  35. data/lib/janeway/version.rb +1 -1
  36. data/lib/janeway.rb +16 -28
  37. metadata +12 -3
  38. data/lib/janeway/ast/query.rb +0 -98
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23479bd7bd49f2ac64ce5e8f27399df191efd5c7efe76aaca4fd5615cf429319
4
- data.tar.gz: b1dea6ae594f5ab51f0105fb32f81ad03a5028223214f38e01c05cae0fad011b
3
+ metadata.gz: ee00e4f5c1c77042409d76e6de5a287dfe0d5bcdaa2c150db2d11fa7e4037228
4
+ data.tar.gz: 7b95f568aa555bb5b9959a2ced3677d7ab7b0e877c69e34db29abbdff5211e50
5
5
  SHA512:
6
- metadata.gz: 46d9ea3bfe2b012d538f53abcad7f29f604ce5c54de51f7f0354d78aa9be4871955cf4666898fdf0ba0e6262147b4d9f29570ca394046b1473597b2c8118f023
7
- data.tar.gz: f323676b0cfeb66a2cbbad9331ff782dbd2691d42258ca607d9e6aa4bf511f6cc6e439962d40720b75a0aceee746f5ed224f4c3f4fbd68c29a2d093395807830
6
+ metadata.gz: 57a64e5946f8375ef13bc634dd9e860588fd7ff7d2c6b2f3b6c14de640ba1ba26e4a4fcc12ba71958853728626d846ed067392ca876efd94f02f0a41c2d2ac5b
7
+ data.tar.gz: 238e8cdb81ddab8674c22eb2150c5b7522e7012eac965f264486017c7e51328a0eea23d3d352698c72eeb0275a1924a1b89575104eaf64d3796a52cdb76499dd
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Janeway JSONPath parser
2
2
 
3
- This is a [JsonPath](https://goessner.net/articles/JsonPath/) parser.
3
+ This is a [JSONPath](https://goessner.net/articles/JsonPath/) parser.
4
4
  It strictly follows [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html) and passes the [JSONPath Compliance Test Suite](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite).
5
5
 
6
6
  It reads a JSON input file and a query, and uses the query to find and return a set of matching values from the input.
@@ -8,8 +8,8 @@ This does for JSON the same job that XPath does for XML.
8
8
 
9
9
  This project includes:
10
10
 
11
- * command-line tool to run jsonpath queries on a JSON input
12
11
  * ruby library to run jsonpath queries on a JSON input
12
+ * command-line tool to do the same
13
13
 
14
14
  **Contents**
15
15
 
@@ -23,13 +23,13 @@ This project includes:
23
23
 
24
24
  Install the gem from the command-line:
25
25
  ```
26
- gem install janeway-jsonpath`
26
+ gem install janeway-jsonpath
27
27
  ```
28
28
 
29
29
  or add it to your Gemfile:
30
30
 
31
31
  ```
32
- gem 'janeway-jsonpath', '~> 0.2.0'
32
+ gem 'janeway-jsonpath', '~> 0.4.0'
33
33
  ```
34
34
 
35
35
  ### Usage
@@ -41,45 +41,92 @@ Use single quotes around the JSON query to avoid shell interaction.
41
41
  Example:
42
42
 
43
43
  ```
44
- $ janeway '$..book[?(@.price<10)]' example.json
45
- [
46
- {
47
- "category": "reference",
48
- "author": "Nigel Rees",
49
- "title": "Sayings of the Century",
50
- "price": 8.95
51
- },
52
- {
53
- "category": "fiction",
54
- "author": "Herman Melville",
55
- "title": "Moby Dick",
56
- "isbn": "0-553-21311-3",
57
- "price": 8.99
44
+ $ cat store.json
45
+ { "store": {
46
+ "book": [
47
+ { "category": "reference",
48
+ "author": "Nigel Rees",
49
+ "title": "Sayings of the Century",
50
+ "price": 8.95
51
+ },
52
+ { "category": "fiction",
53
+ "author": "Evelyn Waugh",
54
+ "title": "Sword of Honour",
55
+ "price": 12.99
56
+ },
57
+ { "category": "fiction",
58
+ "author": "Herman Melville",
59
+ "title": "Moby Dick",
60
+ "isbn": "0-553-21311-3",
61
+ "price": 8.99
62
+ },
63
+ { "category": "fiction",
64
+ "author": "J. R. R. Tolkien",
65
+ "title": "The Lord of the Rings",
66
+ "isbn": "0-395-19395-8",
67
+ "price": 22.99,
68
+ "value": true
69
+ }
70
+ ],
71
+ "bicycle": {
72
+ "color": "red",
73
+ "price": 399
74
+ }
58
75
  }
76
+ }
77
+
78
+
79
+ ### Find and return matching values
80
+ $ janeway '$..book[?(@["price"] == 8.95 || @["price"] == 8.99)].title' store.json
81
+ [
82
+ "Sayings of the Century",
83
+ "Moby Dick"
59
84
  ]
85
+
86
+ ### Delete matching values from the input, print what remains
87
+ $ janeway -d '$.store.book' store.json
88
+ {
89
+ "store": {
90
+ "bicycle": {
91
+ "color": "red",
92
+ "price": 399
93
+ }
94
+ }
95
+ }
60
96
  ```
61
97
 
62
98
  You can also pipe JSON into it:
63
99
  ```
64
- $ cat example.json | janeway '$..book[?(@.price<10)]'
100
+ $ cat store.json | janeway '$..book[?(@.price<10)]'
65
101
  ```
66
102
 
67
103
  See the help message for more capabilities: `janeway --help`
68
104
 
69
105
  #### Janeway ruby libarary
70
106
 
71
- Here's an example of using Janeway to execute a JSONPath query in ruby code:
107
+ Here's an example of ruby code using Janeway to find values from a JSON document:
108
+ To use the Janeway library in ruby code, providing a jsonpath query and an input object (Hash or Array) to search.
72
109
  ```ruby
73
- require 'janeway'
74
- require 'json'
110
+ require 'janeway'
111
+ require 'json'
75
112
 
76
- data = JSON.parse(File.read(ARGV.first))
77
- results = Janeway.find_all('$..book[?(@.price<10)]', data)
113
+ data = JSON.parse(File.read(ARGV.first))
114
+ Janeway.enum_for('$.store.book[0].title', data)
78
115
  ```
116
+ This returns an Enumerator, which offers instance methods for using the query to operate on matching values in the input object.
117
+
118
+ Following are examples showing how to work with the Enumerator methods.
79
119
 
80
- Alternatively, compile the query once, and share it between threads or ractors.
120
+ ##### #search
81
121
 
82
- The Janeway::AST::Query object is not modified after parsing, so it is easy to freeze and share concurrently.
122
+ Returns all values that match the query.
123
+
124
+ ```ruby
125
+ results = Janeway.enum_for('$..book[?(@.price<10)]', data).search
126
+ # Returns every book in the store cheaper than $10
127
+ ```
128
+
129
+ Alternatively, compile the query once, and share it between threads or ractors with different data sources:
83
130
 
84
131
  ```ruby
85
132
  # Create ractors with their own data sources
@@ -88,7 +135,7 @@ The Janeway::AST::Query object is not modified after parsing, so it is easy to f
88
135
  Ractor.new(index) do |i|
89
136
  query = receive
90
137
  data = JSON.parse File.read("input-file-#{i}.json")
91
- puts query.find_all(data)
138
+ puts query.enum_for(data).search
92
139
  end
93
140
  end
94
141
 
@@ -97,6 +144,132 @@ The Janeway::AST::Query object is not modified after parsing, so it is easy to f
97
144
  ractors.each { |ractor| ractor.send(query).take }
98
145
  ```
99
146
 
147
+ ##### #each
148
+
149
+ Iterates through matches one by one, without holding the entire set in memory.
150
+ Janeway's #each iteration is particularly powerful, as it provides context for each match.
151
+ The matched value is yielded:
152
+ ```ruby
153
+ data = {
154
+ 'birds' => ['eagle', 'stork', 'cormorant'] }
155
+ 'dogs' => ['poodle', 'pug', 'saint bernard'] },
156
+ }
157
+ Janeway.enum_for('$.birds.*', data).each do |bird|
158
+ puts "the bird is a #{bird}"
159
+ end
160
+ ```
161
+
162
+ This allows the matched value to be modified in place:
163
+ ```ruby
164
+ Janeway.enum_for('$.birds[? @=="storck"]', data).each do |bird|
165
+ bird.gsub!('ck', 'k') # Fix a typo: "storck" => "stork"
166
+ end
167
+ # input list is now ['eagle', 'stork', 'cormorant']
168
+ ```
169
+
170
+ However, this is not enough to replace the matched value:
171
+ ```ruby
172
+ Janeway.enum_for('$.birds', data).each do |bird|
173
+ bird = "bald eagle" if bird == 'eagle'
174
+ # local variable 'bird' now points to a new value, but the original list is unchanged
175
+ end
176
+ # input list is still ['eagle', 'stork', 'cormorant']
177
+ ```
178
+
179
+ The second and third yield parameters are the object that contains the value, and the array index or hash key of the value.
180
+ This allows the list or hash to be modified:
181
+ ```ruby
182
+ Janeway.enum_for('$.birds[? @=="eagle"]', data).each do |_bird, parent, index|
183
+ parent[index] = "golden eagle"
184
+ end
185
+ # input list is now ['golden eagle', 'stork', 'cormorant']
186
+ ```
187
+
188
+ Lastly, the #each iterator's fourth yield parameter is the [normalized path](https://www.rfc-editor.org/rfc/rfc9535.html#name-normalized-paths) to the matched value.
189
+ This is a jsonpath query string that uniquely points to the matched value.
190
+
191
+ ```ruby
192
+ # Collect the normalized path of every object in the input, at all levels
193
+ paths = []
194
+ Janeway.enum_for('$..*', data).each do |_bird, _parent, _index, path| do
195
+ paths << path
196
+ end
197
+ paths
198
+ # ["$['birds']", "$['dogs']", "$['birds'][0]", "$['birds'][1]", "$['birds'][2]", "$['dogs'][0]", "$['dogs'][1]", "$['dogs'][2]"]
199
+ ```
200
+
201
+ ##### #delete
202
+
203
+ The '#delete' method deletes matched values from the input.
204
+ ```ruby
205
+ # delete any bird whose name is "eagle" or starts with "s"
206
+ Janeway.enum_for('$.birds[? @=="eagle" || search(@, "^s")]', data).delete
207
+ # input bird list is now ['cormorant']
208
+
209
+ # delete all dog names
210
+ Janeway.enum_for('$.dogs.*', data).delete
211
+ # dog list is now []
212
+ ```
213
+
214
+
215
+ The `Janeway.enum_for` and `Janeway::Query#on` methods return an enumerator, so you can use the usual ruby enumerator methods, such as:
216
+
217
+ ##### #map
218
+ Return the matched elements, as modified by the block.
219
+
220
+ ```ruby
221
+ # take a dollar off every price in the store
222
+ sale_prices = Janeway.enum_for('$.store..price', data).map { |price| price - 1 }
223
+ # [7.95, 11.99, 7.99, 21.99, 398]
224
+ ```
225
+
226
+ ##### #select (alias #find_all)
227
+
228
+ Return only values that match the JSONPath query and also return a truthy value from the block.
229
+ This solves a common JSON problem: You want to do a numeric comparison on a value in your JSON, but the JSON value is stored as a string type.
230
+
231
+ ```ruby
232
+ # Poorly serialized, the prices are strings
233
+ data =
234
+ { "store" => {
235
+ "book" => [
236
+ { "title" => "Sayings of the Century", "price" => "8.95" },
237
+ { "title" => "Sword of Honour", "price" => "12.99" },
238
+ { "title" => "Moby Dick", "price" => "8.99" },
239
+ { "title" => "The Lord of the Rings", "price" => "22.99" },
240
+ ]
241
+ }
242
+ }
243
+
244
+ # Can't use a filter query with a numeric comparison on a string price
245
+ Janeway.enum_for('$.store.book[? @.price > 10.00]', data).find_all
246
+ # result: []
247
+
248
+ # Solve the problem with ruby by filtering with #select and converting string to number:
249
+ Janeway.enum_for('$.store.book.*', data).select { |book| book['price'].to_f > 10 }
250
+ # result: [{"title" => "Sword of Honour", "price" => "12.99"}, {"title" => "The Lord of the Rings", "price" => "22.99"}]
251
+ ```
252
+
253
+ ##### #reject
254
+
255
+ Return only values that match the JSONPath query and also return false from the block.
256
+
257
+ ##### #filter_map
258
+
259
+ Combines #select and #map.
260
+ Return values that match the jsonpath query and return truthy values from the block.
261
+ Instead of returning the value from the data, return the result of the block.
262
+
263
+ ##### #find
264
+
265
+ Return the first value that matches the jsonpath query and also returns a truthy value from the block
266
+ ```ruby
267
+ Janeway.enum_for('$.store.book[? @.price >= 10.99]', data).find { |book| book['title'].start_with?('T') }
268
+ # [ "The Lord of the Rings" ]
269
+ ```
270
+
271
+ There are many other Enumerable methods too, see the ruby Enumerable module documenation for more.
272
+
100
273
  ### Related Projects
101
274
 
102
275
  - [joshbuddy/jsonpath](https://github.com/joshbuddy/jsonpath)
@@ -121,7 +294,7 @@ Also there are many non-ruby implementations of RFC 9535, here are just a few:
121
294
  * don't use `eval`, which is known to be an attack vector
122
295
  * be simple and fast with minimal dependencies
123
296
  * provide ruby-like accessors (eg. #each, #delete_if) for processing results
124
- * modern, linted ruby 3 code with frozen string literals
297
+ * idiomatic, linted ruby 3 code with frozen string literals everywhere
125
298
 
126
299
  ### Non-goals
127
300
 
data/bin/janeway CHANGED
@@ -15,14 +15,15 @@ HELP = <<~HELP_TEXT.freeze
15
15
  #{SCRIPT_NAME} [QUERY] [FILENAME]
16
16
 
17
17
  Purpose:
18
- Print the result of applying a JsonPath query to a JSON input.
18
+ Print the result of applying a JSONPath query to a JSON input.
19
19
 
20
- QUERY is a JsonPath query. Quote it with single quotes to avoid shell errors.
20
+ QUERY is a JSONPath query. Quote it with single quotes to avoid shell errors.
21
21
 
22
22
  FILENAME is the path to a JSON file to use as input.
23
23
  Alternately, input JSON can be provided on STDIN.
24
+ If input is not provided then the query is just checked for correctness.
24
25
 
25
- For an introduction to JsonPath, see https://goessner.net/articles/JsonPath/
26
+ For an introduction to JSONPath, see https://goessner.net/articles/JsonPath/
26
27
  For the complete reference, see https://www.rfc-editor.org/info/rfc9535
27
28
 
28
29
  Examples:
@@ -32,7 +33,7 @@ HELP = <<~HELP_TEXT.freeze
32
33
  HELP_TEXT
33
34
 
34
35
  # Command-line options
35
- Options = Struct.new(:query, :query_file, :input, :compact_output, :verbose)
36
+ Options = Struct.new(:query, :query_file, :input, :compact_output, :delete, :verbose)
36
37
 
37
38
  # Parse the command-line arguments.
38
39
  # This includes both bare words and hyphenated options.
@@ -60,6 +61,7 @@ def parse_options(argv)
60
61
 
61
62
  opts.on('-q', '--query FILE', 'Read jsonpath query from file') { |o| options.query_file = o }
62
63
  opts.on('-c', '--compact', 'Express result in compact json format') { options.compact_output = true }
64
+ opts.on('-d', '--delete', 'Print the input, with matching values deleted') { options.delete = true }
63
65
  opts.on('--version', 'Show version number') { abort(Janeway::VERSION) }
64
66
  opts.on('-h', '--help', 'Show this help message') { abort(opts.to_s) }
65
67
  end
@@ -74,8 +76,9 @@ end
74
76
  def read_query(path, argv)
75
77
  return File.read(path).strip if path
76
78
 
77
- query = argv.find { |arg| arg.start_with?('$') }
78
- abort('No JsonPath query received, provide one on the command line.') unless query
79
+ # Assume query is the first arg that is not a filename
80
+ query = argv.reject { File.exist?(_1) }.first
81
+ abort('No JSONPath query received, provide one on the command line.') unless query
79
82
 
80
83
  argv.delete(query)
81
84
  query
@@ -91,7 +94,7 @@ def read_input(path)
91
94
  elsif !$stdin.tty?
92
95
  $stdin.read
93
96
  else
94
- abort('No input JSON provided. Provide a filename or pipe it to STDIN.')
97
+ return # no input json provided
95
98
  end
96
99
  parse_json(json)
97
100
  end
@@ -108,9 +111,31 @@ rescue JSON::JSONError => e
108
111
  abort "Input is not valid JSON: #{msg}"
109
112
  end
110
113
 
114
+ # Just pares the query ane then exit.
115
+ # Useful for testing whether a query is valid.
116
+ # @param query [String] jsonpath
117
+ def parse_query_and_exit(query)
118
+ Janeway.compile(query)
119
+ puts 'Query is valid. Provide input json to run the query.'
120
+ exit(0)
121
+ end
122
+
111
123
  # @param options [Options]
112
124
  def main(options)
113
- results = Janeway.find_all(options.query, options.input)
125
+ parse_query_and_exit(options.query) unless options.input
126
+
127
+ enum = Janeway.enum_for(options.query, options.input)
128
+ if options.delete
129
+ results = enum.delete
130
+ if options.compact_output
131
+ puts JSON.generate(options.input)
132
+ else
133
+ puts JSON.pretty_generate(options.input)
134
+ end
135
+ exit(0)
136
+ else
137
+ results = enum.search
138
+ end
114
139
 
115
140
  if options.compact_output
116
141
  puts JSON.generate(results)
@@ -123,7 +148,9 @@ begin
123
148
  options = parse_args(ARGV.dup)
124
149
  main(options)
125
150
  rescue Janeway::Error => e
126
- abort e.detailed_message
151
+ warn "Error: #{e.message}\nQuery: #{e.query}\n"
152
+ warn " #{' ' * e.location.col}^" if e.location
153
+ exit(1)
127
154
  rescue Interrupt, Errno::EPIPE
128
155
  abort("\n")
129
156
  end
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
-
3
2
  require_relative 'helpers'
4
3
  require_relative 'error'
5
4
 
@@ -23,7 +22,7 @@ module Janeway
23
22
  def initialize(val = nil)
24
23
  # don't set the instance variable if unused, because it makes the
25
24
  # "#inspect" output cleaner in rspec test failures
26
- @value = val unless val.nil? # false must be stored though!
25
+ @value = val unless val.nil? # literal false must be stored though!
27
26
  end
28
27
 
29
28
  # @return [String]
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Janeway
4
+ # Enumerator combines a parsed JSONpath query with input.
5
+ # It provides enumerator methods.
6
+ class Enumerator
7
+ include Enumerable
8
+
9
+ # @param query [Janeway::Query] @param input [Array, Hash]
10
+ def initialize(query, input)
11
+ @query = query
12
+ @input = input
13
+
14
+ raise ArgumentError, "expect Janeway::Query, got #{query.inspect}" unless query.is_a?(Query)
15
+ end
16
+
17
+ # Return a list of values from the input data that match the jsonpath query
18
+ #
19
+ # @return [Array] all matched objects
20
+ def search
21
+ Janeway::Interpreter.new(@query).interpret(@input)
22
+ end
23
+
24
+ # Iterate through each value matched by the JSONPath query.
25
+ #
26
+ # @yieldparam [Object] value matched by query
27
+ # @yieldparam [Array, Hash] parent object that contains the value
28
+ # @yieldparam [String, Integer] hash key or array index of the value within the parent object
29
+ # @yieldparam [String] normalized jsonpath that uniqely points to this value
30
+ # @return [void]
31
+ def each(&block)
32
+ return enum_for(:each) unless block_given?
33
+
34
+ Janeway::Interpreter.new(@query, as: :iterator, &block).interpret(@input)
35
+ end
36
+
37
+ # Delete each value matched by the JSONPath query.
38
+ # @return [Array, Hash]
39
+ def delete
40
+ Janeway::Interpreter.new(@query, as: :deleter).interpret(@input)
41
+ end
42
+ end
43
+ end
data/lib/janeway/error.rb CHANGED
@@ -3,7 +3,10 @@
3
3
  require_relative 'location'
4
4
 
5
5
  module Janeway
6
- # Base class for JSONPath query errors
6
+ # Error class for Janeway.
7
+ # Contains a copy of the jsonpath query string.
8
+ # Lexer errors may also include the index into the query string that
9
+ # points at which token was being lexed at the time of the error.
7
10
  class Error < StandardError
8
11
  # @return [String]
9
12
  attr_reader :query
@@ -14,16 +17,10 @@ module Janeway
14
17
  # @param message [String] error message
15
18
  # @param query [String] entire query string
16
19
  # @param location [Location] location of error
17
- def initialize(message, query = nil, location = nil)
18
- super("Jsonpath query #{query} - #{message}")
20
+ def initialize(msg, query = nil, location = nil)
21
+ super("Jsonpath query #{query} - #{msg}")
19
22
  @query = query
20
23
  @location = location
21
24
  end
22
-
23
- def detailed_message
24
- msg = "Error: #{message}\nQuery: #{query}\n"
25
- msg += "#{' ' * location.col}^" if @location
26
- msg
27
- end
28
25
  end
29
26
  end
@@ -19,7 +19,7 @@ module Janeway
19
19
  unless arg.singular_query? || arg.literal?
20
20
  raise Error, "Invalid parameter - length() expects literal value or singular query, got #{arg.value.inspect}"
21
21
  end
22
- raise Error, 'Too many parameters for length() function call' unless current.type == :group_end
22
+ raise err('Too many parameters for length() function call') unless current.type == :group_end
23
23
 
24
24
  # Meaning of return value depends on the JSON type:
25
25
  # * string - number of Unicode scalar values in the string.