json_p3 0.4.0 → 1.0.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 (76) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.rubocop.yml +26 -7
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +58 -0
  6. data/README.md +125 -123
  7. data/Rakefile +3 -3
  8. data/certs/jgrp.pem +21 -21
  9. data/lib/json_p3/errors.rb +51 -43
  10. data/lib/json_p3/patch/op.rb +23 -0
  11. data/lib/json_p3/patch/op_add.rb +51 -0
  12. data/lib/json_p3/patch/op_copy.rb +64 -0
  13. data/lib/json_p3/patch/op_move.rb +74 -0
  14. data/lib/json_p3/patch/op_remove.rb +56 -0
  15. data/lib/json_p3/patch/op_replace.rb +54 -0
  16. data/lib/json_p3/patch/op_test.rb +31 -0
  17. data/lib/json_p3/patch.rb +15 -330
  18. data/lib/json_p3/path/environment.rb +113 -0
  19. data/lib/json_p3/path/filter.rb +463 -0
  20. data/lib/json_p3/path/function.rb +12 -0
  21. data/lib/json_p3/path/function_extensions/count.rb +15 -0
  22. data/lib/json_p3/path/function_extensions/length.rb +17 -0
  23. data/lib/json_p3/path/function_extensions/match.rb +62 -0
  24. data/lib/json_p3/path/function_extensions/pattern.rb +42 -0
  25. data/lib/json_p3/path/function_extensions/search.rb +44 -0
  26. data/lib/json_p3/path/function_extensions/value.rb +15 -0
  27. data/lib/json_p3/path/lexer.rb +220 -0
  28. data/lib/json_p3/path/node.rb +48 -0
  29. data/lib/json_p3/path/parser.rb +676 -0
  30. data/lib/json_p3/path/query.rb +74 -0
  31. data/lib/json_p3/path/segment.rb +172 -0
  32. data/lib/json_p3/path/selector.rb +304 -0
  33. data/lib/json_p3/path/serialize.rb +16 -0
  34. data/lib/json_p3/path/unescape.rb +134 -0
  35. data/lib/json_p3/pointer.rb +15 -76
  36. data/lib/json_p3/relative_pointer.rb +69 -0
  37. data/lib/json_p3/version.rb +1 -1
  38. data/lib/json_p3.rb +50 -13
  39. data/sig/json_p3/cache.rbs +21 -0
  40. data/sig/json_p3/errors.rbs +55 -0
  41. data/sig/json_p3/patch.rbs +145 -0
  42. data/sig/json_p3/path/environment.rbs +81 -0
  43. data/sig/json_p3/path/filter.rbs +196 -0
  44. data/sig/json_p3/path/function.rbs +94 -0
  45. data/sig/json_p3/path/lexer.rbs +62 -0
  46. data/sig/json_p3/path/node.rbs +46 -0
  47. data/sig/json_p3/path/parser.rbs +92 -0
  48. data/sig/json_p3/path/query.rbs +47 -0
  49. data/sig/json_p3/path/segment.rbs +54 -0
  50. data/sig/json_p3/path/selector.rbs +100 -0
  51. data/sig/json_p3/path/serialize.rbs +9 -0
  52. data/sig/json_p3/path/unescape.rbs +12 -0
  53. data/sig/json_p3/pointer.rbs +64 -0
  54. data/sig/json_p3/relative_pointer.rbs +30 -0
  55. data/sig/json_p3.rbs +24 -1313
  56. data.tar.gz.sig +0 -0
  57. metadata +66 -46
  58. metadata.gz.sig +0 -0
  59. data/lib/json_p3/environment.rb +0 -111
  60. data/lib/json_p3/filter.rb +0 -459
  61. data/lib/json_p3/function.rb +0 -10
  62. data/lib/json_p3/function_extensions/count.rb +0 -15
  63. data/lib/json_p3/function_extensions/length.rb +0 -17
  64. data/lib/json_p3/function_extensions/match.rb +0 -62
  65. data/lib/json_p3/function_extensions/pattern.rb +0 -39
  66. data/lib/json_p3/function_extensions/search.rb +0 -44
  67. data/lib/json_p3/function_extensions/value.rb +0 -15
  68. data/lib/json_p3/lexer.rb +0 -419
  69. data/lib/json_p3/node.rb +0 -44
  70. data/lib/json_p3/parser.rb +0 -553
  71. data/lib/json_p3/path.rb +0 -72
  72. data/lib/json_p3/segment.rb +0 -158
  73. data/lib/json_p3/selector.rb +0 -306
  74. data/lib/json_p3/serialize.rb +0 -13
  75. data/lib/json_p3/token.rb +0 -36
  76. data/lib/json_p3/unescape.rb +0 -112
@@ -1,553 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
- require "set"
5
-
6
- require_relative "errors"
7
- require_relative "filter"
8
- require_relative "function"
9
- require_relative "segment"
10
- require_relative "selector"
11
- require_relative "token"
12
- require_relative "unescape"
13
-
14
- module JSONP3
15
- # Step through tokens
16
- class Stream
17
- def initialize(tokens)
18
- @tokens = tokens
19
- @index = 0
20
- @eoi = tokens.last
21
- end
22
-
23
- def next
24
- token = @tokens.fetch(@index)
25
- @index += 1
26
- token
27
- rescue IndexError
28
- @eor
29
- end
30
-
31
- def peek
32
- @tokens.fetch(@index)
33
- rescue IndexError
34
- @eor
35
- end
36
-
37
- def expect(token_type)
38
- return if peek.type == token_type
39
-
40
- token = self.next
41
- raise JSONPathSyntaxError.new("expected #{token_type}, found #{token.type}", token)
42
- end
43
-
44
- def expect_not(token_type, message)
45
- return unless peek.type == token_type
46
-
47
- token = self.next
48
- raise JSONPathSyntaxError.new(message, token)
49
- end
50
-
51
- def to_s
52
- "JSONP3::stream(head=#{peek.inspect})"
53
- end
54
- end
55
-
56
- class Precedence
57
- LOWEST = 1
58
- LOGICAL_OR = 3
59
- LOGICAL_AND = 4
60
- RELATIONAL = 5
61
- PREFIX = 7
62
- end
63
-
64
- # A JSONPath expression parser.
65
- class Parser
66
- def initialize(env)
67
- @env = env
68
- @name_selector = env.class::NAME_SELECTOR
69
- @index_selector = env.class::INDEX_SELECTOR
70
- end
71
-
72
- # Parse an array of tokens into an abstract syntax tree.
73
- # @param tokens [Array<Token>] tokens from the lexer.
74
- # @return [Array<Segment>]
75
- def parse(tokens)
76
- stream = Stream.new(tokens)
77
- stream.expect(:token_root)
78
- stream.next
79
- parse_query(stream)
80
- end
81
-
82
- protected
83
-
84
- def parse_query(stream)
85
- segments = [] # : Array[Segment]
86
-
87
- loop do
88
- case stream.peek.type
89
- when :token_double_dot
90
- token = stream.next
91
- selectors = parse_selectors(stream)
92
- segments << RecursiveDescentSegment.new(@env, token, selectors)
93
- when :token_lbracket, :token_name, :token_wild
94
- token = stream.peek
95
- selectors = parse_selectors(stream)
96
- segments << ChildSegment.new(@env, token, selectors)
97
- else
98
- break
99
- end
100
- end
101
-
102
- segments
103
- end
104
-
105
- def parse_selectors(stream)
106
- case stream.peek.type
107
- when :token_name
108
- token = stream.next
109
- [@name_selector.new(@env, token, token.value)]
110
- when :token_wild
111
- [WildcardSelector.new(@env, stream.next)]
112
- when :token_lbracket
113
- parse_bracketed_selection(stream)
114
- else
115
- []
116
- end
117
- end
118
-
119
- def parse_bracketed_selection(stream)
120
- stream.expect(:token_lbracket)
121
- segment_token = stream.next
122
-
123
- selectors = [] # : Array[Selector]
124
-
125
- loop do # rubocop:disable Metrics/BlockLength
126
- case stream.peek.type
127
- when :token_rbracket
128
- break
129
- when :token_index
130
- selectors << parse_index_or_slice(stream)
131
- when :token_double_quote_string, :token_single_quote_string
132
- token = stream.next
133
- selectors << @name_selector.new(@env, token, decode_string_literal(token))
134
- when :token_colon
135
- selectors << parse_slice_selector(stream)
136
- when :token_wild
137
- selectors << WildcardSelector.new(@env, stream.next)
138
- when :token_filter
139
- selectors << parse_filter_selector(stream)
140
- when :token_eoi
141
- raise JSONPathSyntaxError.new("unexpected end of query", stream.next)
142
- else
143
- raise JSONPathSyntaxError.new("unexpected token in bracketed selection", stream.next)
144
- end
145
-
146
- case stream.peek.type
147
- when :token_eoi
148
- raise JSONPathSyntaxError.new("unexpected end of selector list", stream.next)
149
- when :token_rbracket
150
- break
151
- else
152
- stream.expect(:token_comma)
153
- stream.next
154
- stream.expect_not(:token_rbracket, "unexpected trailing comma")
155
- end
156
- end
157
-
158
- stream.expect(:token_rbracket)
159
- stream.next
160
-
161
- raise JSONPathSyntaxError.new("empty segment", segment_token) if selectors.empty?
162
-
163
- selectors
164
- end
165
-
166
- def parse_index_or_slice(stream)
167
- token = stream.next
168
- index = parse_i_json_int(token)
169
-
170
- return @index_selector.new(@env, token, index) unless stream.peek.type == :token_colon
171
-
172
- stream.next # move past colon
173
- stop = nil
174
- step = nil
175
-
176
- case stream.peek.type
177
- when :token_index
178
- stop = parse_i_json_int(stream.next)
179
- when :token_colon
180
- stream.next # move past colon
181
- end
182
-
183
- stream.next if stream.peek.type == :token_colon
184
-
185
- case stream.peek.type
186
- when :token_index
187
- step = parse_i_json_int(stream.next)
188
- when :token_rbracket
189
- nil
190
- else
191
- error_token = stream.next
192
- raise JSONPathSyntaxError.new("expected a slice, found '#{error_token.value}'", error_token)
193
- end
194
-
195
- SliceSelector.new(@env, token, index, stop, step)
196
- end
197
-
198
- def parse_slice_selector(stream)
199
- stream.expect(:token_colon)
200
- token = stream.next
201
-
202
- start = nil
203
- stop = nil
204
- step = nil
205
-
206
- case stream.peek.type
207
- when :token_index
208
- stop = parse_i_json_int(stream.next)
209
- when :token_colon
210
- stream.next # move past colon
211
- end
212
-
213
- stream.next if stream.peek.type == :token_colon
214
-
215
- case stream.peek.type
216
- when :token_index
217
- step = parse_i_json_int(stream.next)
218
- when :token_rbracket
219
- nil
220
- else
221
- error_token = stream.next
222
- raise JSONPathSyntaxError.new("expected a slice, found '#{token.value}'", error_token)
223
- end
224
-
225
- SliceSelector.new(@env, token, start, stop, step)
226
- end
227
-
228
- def parse_filter_selector(stream)
229
- token = stream.next
230
- expression = parse_filter_expression(stream)
231
-
232
- # Raise if expression must be compared.
233
- if expression.is_a? FunctionExpression
234
- func = @env.function_extensions[expression.name]
235
- if func.class::RETURN_TYPE == :value_expression
236
- raise JSONPathTypeError.new("result of #{expression.name}() must be compared", expression.token)
237
- end
238
- end
239
-
240
- # Raise if expression is a literal.
241
- if expression.is_a? FilterExpressionLiteral
242
- raise JSONPathSyntaxError.new("filter expression literals must be compared", expression.token)
243
- end
244
-
245
- FilterSelector.new(@env, token, FilterExpression.new(token, expression))
246
- end
247
-
248
- def parse_filter_expression(stream, precedence = Precedence::LOWEST)
249
- left = case stream.peek.type
250
- when :token_double_quote_string, :token_single_quote_string
251
- token = stream.next
252
- StringLiteral.new(token, decode_string_literal(token))
253
- when :token_false
254
- BooleanLiteral.new(stream.next, false)
255
- when :token_true
256
- BooleanLiteral.new(stream.next, true)
257
- when :token_float
258
- parse_float_literal(stream)
259
- when :token_function
260
- parse_function_expression(stream)
261
- when :token_int
262
- parse_integer_literal(stream)
263
- when :token_lparen
264
- parse_grouped_expression(stream)
265
- when :token_not
266
- parse_prefix_expression(stream)
267
- when :token_null
268
- NullLiteral.new(stream.next, nil)
269
- when :token_root
270
- parse_root_query(stream)
271
- when :token_current
272
- parse_relative_query(stream)
273
- else
274
- token = stream.next
275
- raise JSONPathSyntaxError.new("unexpected '#{token.value}'", token)
276
- end
277
-
278
- loop do
279
- peeked = stream.peek
280
- if peeked.type == :token_eoi ||
281
- peeked.type == :token_rbracket ||
282
- PRECEDENCES.fetch(peeked.type, Precedence::LOWEST) < precedence
283
- break
284
- end
285
-
286
- return left unless BINARY_OPERATORS.key?(peeked.type)
287
-
288
- left = parse_infix_expression(stream, left)
289
- end
290
-
291
- left
292
- end
293
-
294
- def parse_integer_literal(stream)
295
- token = stream.next
296
- value = token.value
297
- raise JSONPathSyntaxError.new("invalid integer literal", token) if value.start_with?("0") && value.length > 1
298
-
299
- IntegerLiteral.new(token, Integer(Float(token.value)))
300
- end
301
-
302
- def parse_float_literal(stream)
303
- token = stream.next
304
- value = token.value
305
- if value.start_with?("0") && value.split(".").first.length > 1
306
- raise JSONPathSyntaxError.new("invalid float literal", token)
307
- end
308
-
309
- begin
310
- FloatLiteral.new(token, Float(value))
311
- rescue ArgumentError
312
- raise JSONPathSyntaxError.new("invalid float literal", token)
313
- end
314
- end
315
-
316
- def parse_function_expression(stream)
317
- token = stream.next
318
- args = [] # : Array[Expression]
319
-
320
- while stream.peek.type != :token_rparen
321
- expr = case stream.peek.type
322
- when :token_double_quote_string, :token_single_quote_string
323
- arg_token = stream.next
324
- StringLiteral.new(arg_token, decode_string_literal(arg_token))
325
- when :token_false
326
- BooleanLiteral.new(stream.next, false)
327
- when :token_true
328
- BooleanLiteral.new(stream.next, true)
329
- when :token_float
330
- parse_float_literal(stream)
331
- when :token_function
332
- parse_function_expression(stream)
333
- when :token_int
334
- parse_integer_literal(stream)
335
- when :token_null
336
- NullLiteral.new(stream.next, nil)
337
- when :token_root
338
- parse_root_query(stream)
339
- when :token_current
340
- parse_relative_query(stream)
341
- else
342
- arg_token = stream.next
343
- raise JSONPathSyntaxError.new("unexpected '#{arg_token.value}'", arg_token)
344
- end
345
-
346
- expr = parse_infix_expression(stream, expr) while BINARY_OPERATORS.key? stream.peek.type
347
-
348
- args << expr
349
-
350
- if stream.peek.type != :token_rparen
351
- stream.expect(:token_comma)
352
- stream.next
353
- end
354
- end
355
-
356
- stream.expect(:token_rparen)
357
- stream.next
358
-
359
- validate_function_extension_signature(token, args)
360
- FunctionExpression.new(token, token.value, args)
361
- end
362
-
363
- def parse_grouped_expression(stream)
364
- stream.next # discard "("
365
- expr = parse_filter_expression(stream)
366
-
367
- while stream.peek.type != :token_rparen
368
- raise JSONPathSyntaxError.new("unbalanced parentheses", stream.peek) if stream.peek.type == :token_eoi
369
-
370
- expr = parse_infix_expression(stream, expr)
371
- end
372
-
373
- stream.expect(:token_rparen)
374
- stream.next
375
- expr
376
- end
377
-
378
- def parse_prefix_expression(stream)
379
- token = stream.next
380
- LogicalNotExpression.new(token, parse_filter_expression(stream, Precedence::PREFIX))
381
- end
382
-
383
- def parse_root_query(stream)
384
- token = stream.next
385
- RootQueryExpression.new(token, JSONPath.new(@env, parse_query(stream)))
386
- end
387
-
388
- def parse_relative_query(stream)
389
- token = stream.next
390
- RelativeQueryExpression.new(token, JSONPath.new(@env, parse_query(stream)))
391
- end
392
-
393
- def parse_infix_expression(stream, left)
394
- token = stream.next
395
- precedence = PRECEDENCES.fetch(token.type, Precedence::LOWEST)
396
- right = parse_filter_expression(stream, precedence)
397
-
398
- if COMPARISON_OPERATORS.member? token.value
399
- raise_for_non_comparable_function(left)
400
- raise_for_non_comparable_function(right)
401
- case token.type
402
- when :token_eq
403
- EqExpression.new(token, left, right)
404
- when :token_ge
405
- GeExpression.new(token, left, right)
406
- when :token_gt
407
- GtExpression.new(token, left, right)
408
- when :token_le
409
- LeExpression.new(token, left, right)
410
- when :token_lt
411
- LtExpression.new(token, left, right)
412
- when :token_ne
413
- NeExpression.new(token, left, right)
414
- else
415
- raise JSONPathSyntaxError.new("unexpected token", token)
416
- end
417
- else
418
- raise_for_not_compared_literal(left)
419
- raise_for_not_compared_literal(right)
420
- case token.type
421
- when :token_and
422
- LogicalAndExpression.new(token, left, right)
423
- when :token_or
424
- LogicalOrExpression.new(token, left, right)
425
- else
426
- raise JSONPathSyntaxError.new("unexpected token", token)
427
- end
428
- end
429
- end
430
-
431
- def parse_i_json_int(token)
432
- value = token.value
433
-
434
- if value.length > 1 && value.start_with?("0", "-0")
435
- raise JSONPathSyntaxError.new("invalid index '#{value}'", token)
436
- end
437
-
438
- begin
439
- int = Integer(value)
440
- rescue ArgumentError
441
- raise JSONPathSyntaxError.new("invalid I-JSON integer", token)
442
- end
443
-
444
- if int < @env.class::MIN_INT_INDEX || int > @env.class::MAX_INT_INDEX
445
- raise JSONPathSyntaxError.new("index out of range",
446
- token)
447
- end
448
-
449
- int
450
- end
451
-
452
- def decode_string_literal(token)
453
- if token.type == :token_single_quote_string
454
- JSONP3.unescape_string(token.value, "'", token)
455
- else
456
- JSONP3.unescape_string(token.value, '"', token)
457
- end
458
- end
459
-
460
- def raise_for_non_comparable_function(expression)
461
- if expression.is_a?(QueryExpression) && !expression.query.singular?
462
- raise JSONPathSyntaxError.new("non-singular query is not comparable", expression.token)
463
- end
464
-
465
- return unless expression.is_a?(FunctionExpression)
466
-
467
- func = @env.function_extensions[expression.name]
468
- return unless func.class::RETURN_TYPE != :value_expression
469
-
470
- raise JSONPathTypeError.new("result of #{expression.name}() is not comparable", expression.token)
471
- end
472
-
473
- def raise_for_not_compared_literal(expression)
474
- return unless expression.is_a? FilterExpressionLiteral
475
-
476
- raise JSONPathSyntaxError.new("expression literals must be compared",
477
- expression.token)
478
- end
479
-
480
- def validate_function_extension_signature(token, args)
481
- func = @env.function_extensions.fetch(token.value)
482
- count = func.class::ARG_TYPES.length
483
-
484
- unless args.length == count
485
- raise JSONPathTypeError.new(
486
- "#{token.value}() takes #{count} argument#{count == 1 ? "" : "s"} (#{args.length} given)",
487
- token
488
- )
489
- end
490
-
491
- func.class::ARG_TYPES.each_with_index do |t, i|
492
- arg = args[i]
493
- case t
494
- when :value_expression
495
- unless arg.is_a?(FilterExpressionLiteral) ||
496
- (arg.is_a?(QueryExpression) && arg.query.singular?) ||
497
- (function_return_type(arg) == :value_expression)
498
- raise JSONPathTypeError.new("#{token.value}() argument #{i} must be of ValueType", arg.token)
499
- end
500
- when :logical_expression
501
- unless arg.is_a?(QueryExpression) || arg.is_a?(InfixExpression)
502
- raise JSONPathTypeError.new("#{token.value}() argument #{i} must be of LogicalType", arg.token)
503
- end
504
- when :nodes_expression
505
- unless arg.is_a?(QueryExpression) || function_return_type(arg) == :nodes_expression
506
- raise JSONPathTypeError.new("#{token.value}() argument #{i} must be of NodesType", arg.token)
507
- end
508
- end
509
- end
510
- rescue KeyError
511
- raise JSONPathNameError.new("function '#{token.value}' is not defined", token)
512
- end
513
-
514
- def function_return_type(expression)
515
- return nil unless expression.is_a? FunctionExpression
516
-
517
- @env.function_extensions[expression.name].class::RETURN_TYPE
518
- end
519
-
520
- PRECEDENCES = {
521
- token_and: Precedence::LOGICAL_AND,
522
- token_or: Precedence::LOGICAL_OR,
523
- token_not: Precedence::PREFIX,
524
- token_eq: Precedence::RELATIONAL,
525
- token_ge: Precedence::RELATIONAL,
526
- token_gt: Precedence::RELATIONAL,
527
- token_le: Precedence::RELATIONAL,
528
- token_lt: Precedence::RELATIONAL,
529
- token_ne: Precedence::RELATIONAL,
530
- token_rparen: Precedence::LOWEST
531
- }.freeze
532
-
533
- BINARY_OPERATORS = {
534
- token_and: "&&",
535
- token_or: "||",
536
- token_eq: "==",
537
- token_ge: ">=",
538
- token_gt: ">",
539
- token_le: "<=",
540
- token_lt: "<",
541
- token_ne: "!="
542
- }.freeze
543
-
544
- COMPARISON_OPERATORS = Set[
545
- "==",
546
- ">=",
547
- ">",
548
- "<=",
549
- "<",
550
- "!=",
551
- ]
552
- end
553
- end
data/lib/json_p3/path.rb DELETED
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "node"
4
-
5
- module JSONP3
6
- # A compiled JSONPath expression ready to be applied to JSON-like values.
7
- class JSONPath
8
- def initialize(env, segments)
9
- @env = env
10
- @segments = segments
11
- end
12
-
13
- def to_s
14
- "$#{@segments.map(&:to_s).join}"
15
- end
16
-
17
- # Apply this JSONPath expression to JSON-like value _root_.
18
- # @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
19
- # @return [Array<JSONPathNode>] the sequence of nodes found while applying this query to _root_.
20
- def find(root)
21
- nodes = [JSONPathNode.new(root, [], root)]
22
- @segments.each { |segment| nodes = segment.resolve(nodes) }
23
- JSONPathNodeList.new(nodes) # TODO: use JSONPathNodeList internally?
24
- end
25
-
26
- alias apply find
27
-
28
- # Apply this JSONPath expression to JSON-like value _root_.
29
- # @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
30
- # @return [Enumerable<JSONPathNode>] the sequence of nodes found while applying this query to _root_.
31
- def find_enum(root)
32
- nodes = [JSONPathNode.new(root, [], root)] # : Enumerable[JSONPathNode]
33
- @segments.each { |segment| nodes = segment.resolve_enum(nodes) }
34
- nodes
35
- end
36
-
37
- # Return the first node from applying this JSONPath expression to JSON-like value _root_.
38
- # @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
39
- # @return [JSONPathNode | nil] the first available node or nil if there were no matches.
40
- def match(root)
41
- find_enum(root).first
42
- end
43
-
44
- # Return `true` if this query results in at least one node, or `false` otherwise.
45
- # @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
46
- # @return [bool] `true` if this query results in at least one node, or `false` otherwise.
47
- def match?(root)
48
- !find_enum(root).first.nil?
49
- end
50
-
51
- # Return the first node from applying this JSONPath expression to JSON-like value _root_.
52
- # @param root [Array, Hash, String, Integer, nil] the root JSON-like value to apply this query to.
53
- # @return [JSONPathNode | nil] the first available node or nil if there were no matches.
54
- def first(root)
55
- find_enum(root).first
56
- end
57
-
58
- # Return _true_ if this JSONPath expression is a singular query.
59
- def singular?
60
- @segments.each do |segment|
61
- return false if segment.instance_of? RecursiveDescentSegment
62
- return false unless segment.selectors.length == 1 && segment.selectors[0].singular?
63
- end
64
- true
65
- end
66
-
67
- # Return _true_ if this JSONPath expression has no segments.
68
- def empty?
69
- @segments.empty?
70
- end
71
- end
72
- end