json_path_rfc9535 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.
- checksums.yaml +7 -0
- data/.rubocop.yml +455 -0
- data/CHANGELOG.md +13 -0
- data/README.md +126 -0
- data/lib/json_path/doc.rb +27 -0
- data/lib/json_path/error.rb +4 -0
- data/lib/json_path/multiple_values_returned_by_singular_query.rb +6 -0
- data/lib/json_path/node_list.rb +27 -0
- data/lib/json_path/nodes/array.rb +19 -0
- data/lib/json_path/nodes/base.rb +13 -0
- data/lib/json_path/nodes/false.rb +9 -0
- data/lib/json_path/nodes/null.rb +9 -0
- data/lib/json_path/nodes/number.rb +12 -0
- data/lib/json_path/nodes/object.rb +19 -0
- data/lib/json_path/nodes/string.rb +12 -0
- data/lib/json_path/nodes/true.rb +9 -0
- data/lib/json_path/nodes.rb +27 -0
- data/lib/json_path/parser/core.rb +75 -0
- data/lib/json_path/parser/raw_parser.rb +73 -0
- data/lib/json_path/parser/transformer.rb +219 -0
- data/lib/json_path/parser.rb +16 -0
- data/lib/json_path/path.rb +22 -0
- data/lib/json_path/unrecognized_node.rb +4 -0
- data/lib/json_path.rb +7 -0
- data/lib/json_path_rfc9535/version.rb +3 -0
- data/lib/json_path_rfc9535.rb +2 -0
- metadata +89 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 341631a563a6872bcf0f04f58fdb547a5a17500f3f7fae04ba29ada044c17dbd
|
|
4
|
+
data.tar.gz: '0669414a8c33cd4ddfd67f2e330d72783ec862ba1a26573053a5bbf372a6b26e'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c355da298d64c50f35f59a76bbff44b2511de1dafbf65181a078db74909433396dd86f803ecd1b821ce0bf4327264786298d7189e5c7ee37777bf35e53dacc8e
|
|
7
|
+
data.tar.gz: f8e4f5e8a131adb0b1d88b9a820218687db2a146fea1a4ef91e33adb461b1d299285895c5fde88554f6ac14d87b7e86235ed97734253f397e06e741a5f270b13
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
NewCops: enable
|
|
3
|
+
Exclude:
|
|
4
|
+
- 'bin/**'
|
|
5
|
+
|
|
6
|
+
Gemspec/DeprecatedAttributeAssignment:
|
|
7
|
+
Enabled: true
|
|
8
|
+
|
|
9
|
+
Gemspec/DevelopmentDependencies:
|
|
10
|
+
Enabled: true
|
|
11
|
+
|
|
12
|
+
Gemspec/RequireMFA:
|
|
13
|
+
Enabled: false
|
|
14
|
+
|
|
15
|
+
Layout/AccessModifierIndentation:
|
|
16
|
+
EnforcedStyle: outdent
|
|
17
|
+
|
|
18
|
+
Layout/BeginEndAlignment:
|
|
19
|
+
EnforcedStyleAlignWith: begin
|
|
20
|
+
|
|
21
|
+
Layout/BlockAlignment:
|
|
22
|
+
EnforcedStyleAlignWith: start_of_block
|
|
23
|
+
|
|
24
|
+
Layout/CommentIndentation:
|
|
25
|
+
AllowForAlignment: true
|
|
26
|
+
|
|
27
|
+
Layout/EmptyLineAfterMultilineCondition:
|
|
28
|
+
Enabled: true
|
|
29
|
+
|
|
30
|
+
Layout/EndOfLine:
|
|
31
|
+
EnforcedStyle: lf
|
|
32
|
+
|
|
33
|
+
Layout/ExtraSpacing:
|
|
34
|
+
AllowForAlignment: true
|
|
35
|
+
AllowBeforeTrailingComments: true
|
|
36
|
+
|
|
37
|
+
Layout/HashAlignment:
|
|
38
|
+
EnforcedHashRocketStyle: table
|
|
39
|
+
EnforcedColonStyle: table
|
|
40
|
+
EnforcedLastArgumentHashStyle: ignore_implicit
|
|
41
|
+
|
|
42
|
+
Layout/LineContinuationLeadingSpace:
|
|
43
|
+
Enabled: true
|
|
44
|
+
|
|
45
|
+
Layout/LineContinuationSpacing:
|
|
46
|
+
Enabled: true
|
|
47
|
+
|
|
48
|
+
Layout/LineEndStringConcatenationIndentation:
|
|
49
|
+
Enabled: true
|
|
50
|
+
|
|
51
|
+
Layout/MultilineArrayLineBreaks:
|
|
52
|
+
Enabled: true
|
|
53
|
+
|
|
54
|
+
Layout/MultilineAssignmentLayout:
|
|
55
|
+
Enabled: true
|
|
56
|
+
EnforcedStyle: same_line
|
|
57
|
+
|
|
58
|
+
Layout/MultilineHashKeyLineBreaks:
|
|
59
|
+
Enabled: true
|
|
60
|
+
|
|
61
|
+
Layout/MultilineMethodArgumentLineBreaks:
|
|
62
|
+
Enabled: true
|
|
63
|
+
|
|
64
|
+
Layout/MultilineMethodCallIndentation:
|
|
65
|
+
EnforcedStyle: indented_relative_to_receiver
|
|
66
|
+
|
|
67
|
+
Layout/SingleLineBlockChain:
|
|
68
|
+
Enabled: true
|
|
69
|
+
|
|
70
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
|
71
|
+
EnforcedStyle: no_space
|
|
72
|
+
|
|
73
|
+
Layout/SpaceAroundOperators:
|
|
74
|
+
EnforcedStyleForExponentOperator: space
|
|
75
|
+
|
|
76
|
+
Layout/SpaceBeforeBrackets:
|
|
77
|
+
Enabled: true
|
|
78
|
+
|
|
79
|
+
Layout/SpaceInsideHashLiteralBraces:
|
|
80
|
+
EnforcedStyle: no_space
|
|
81
|
+
|
|
82
|
+
Layout/TrailingWhitespace:
|
|
83
|
+
AllowInHeredoc: true
|
|
84
|
+
|
|
85
|
+
Lint/AmbiguousAssignment:
|
|
86
|
+
Enabled: true
|
|
87
|
+
|
|
88
|
+
Lint/AmbiguousOperatorPrecedence:
|
|
89
|
+
Enabled: true
|
|
90
|
+
|
|
91
|
+
Lint/AmbiguousRange:
|
|
92
|
+
Enabled: true
|
|
93
|
+
RequireParenthesesForMethodChains: true
|
|
94
|
+
|
|
95
|
+
Lint/AssignmentInCondition:
|
|
96
|
+
AllowSafeAssignment: false
|
|
97
|
+
|
|
98
|
+
Lint/ConstantOverwrittenInRescue:
|
|
99
|
+
Enabled: true
|
|
100
|
+
|
|
101
|
+
Lint/DeprecatedConstants:
|
|
102
|
+
Enabled: true
|
|
103
|
+
|
|
104
|
+
Lint/DuplicateBranch:
|
|
105
|
+
Enabled: true
|
|
106
|
+
IgnoreLiteralBranches: true
|
|
107
|
+
IgnoreConstantBranches: true
|
|
108
|
+
|
|
109
|
+
Lint/DuplicateMagicComment:
|
|
110
|
+
Enabled: true
|
|
111
|
+
|
|
112
|
+
Lint/DuplicateRegexpCharacterClassElement:
|
|
113
|
+
Enabled: true
|
|
114
|
+
|
|
115
|
+
Lint/EmptyBlock:
|
|
116
|
+
Enabled: true
|
|
117
|
+
|
|
118
|
+
Lint/EmptyClass:
|
|
119
|
+
Enabled: true
|
|
120
|
+
AllowComments: true
|
|
121
|
+
|
|
122
|
+
Lint/EmptyInPattern:
|
|
123
|
+
Enabled: true
|
|
124
|
+
|
|
125
|
+
Lint/HeredocMethodCallPosition:
|
|
126
|
+
Enabled: true
|
|
127
|
+
|
|
128
|
+
Lint/IncompatibleIoSelectWithFiberScheduler:
|
|
129
|
+
Enabled: false
|
|
130
|
+
|
|
131
|
+
Lint/LambdaWithoutLiteralBlock:
|
|
132
|
+
Enabled: true
|
|
133
|
+
|
|
134
|
+
Lint/NoReturnInBeginEndBlocks:
|
|
135
|
+
Enabled: true
|
|
136
|
+
|
|
137
|
+
Lint/NonAtomicFileOperation:
|
|
138
|
+
Enabled: true
|
|
139
|
+
|
|
140
|
+
Lint/NumberedParameterAssignment:
|
|
141
|
+
Enabled: true
|
|
142
|
+
|
|
143
|
+
Lint/OrAssignmentToConstant:
|
|
144
|
+
Enabled: true
|
|
145
|
+
|
|
146
|
+
Lint/RedundantDirGlobSort:
|
|
147
|
+
Enabled: true
|
|
148
|
+
|
|
149
|
+
Lint/RedundantSplatExpansion:
|
|
150
|
+
AllowPercentLiteralArrayArgument: false
|
|
151
|
+
|
|
152
|
+
Lint/RefinementImportMethods:
|
|
153
|
+
Enabled: true
|
|
154
|
+
|
|
155
|
+
Lint/RequireRangeParentheses:
|
|
156
|
+
Enabled: true
|
|
157
|
+
|
|
158
|
+
Lint/RequireRelativeSelfPath:
|
|
159
|
+
Enabled: true
|
|
160
|
+
|
|
161
|
+
Lint/SymbolConversion:
|
|
162
|
+
Enabled: true
|
|
163
|
+
|
|
164
|
+
Lint/ToEnumArguments:
|
|
165
|
+
Enabled: true
|
|
166
|
+
|
|
167
|
+
Lint/TripleQuotes:
|
|
168
|
+
Enabled: true
|
|
169
|
+
|
|
170
|
+
Lint/UnexpectedBlockArity:
|
|
171
|
+
Enabled: true
|
|
172
|
+
|
|
173
|
+
Lint/UnmodifiedReduceAccumulator:
|
|
174
|
+
Enabled: true
|
|
175
|
+
|
|
176
|
+
Lint/UnusedBlockArgument:
|
|
177
|
+
AutoCorrect: false
|
|
178
|
+
|
|
179
|
+
Lint/UnusedMethodArgument:
|
|
180
|
+
AutoCorrect: false
|
|
181
|
+
|
|
182
|
+
Lint/UselessRescue:
|
|
183
|
+
Enabled: true
|
|
184
|
+
|
|
185
|
+
Lint/UselessRuby2Keywords:
|
|
186
|
+
Enabled: true
|
|
187
|
+
|
|
188
|
+
Metrics:
|
|
189
|
+
Enabled: false
|
|
190
|
+
|
|
191
|
+
Naming/BlockForwarding:
|
|
192
|
+
Enabled: true
|
|
193
|
+
|
|
194
|
+
Naming/InclusiveLanguage:
|
|
195
|
+
Enabled: false
|
|
196
|
+
|
|
197
|
+
Security/CompoundHash:
|
|
198
|
+
Enabled: true
|
|
199
|
+
|
|
200
|
+
Security/Eval:
|
|
201
|
+
Enabled: false
|
|
202
|
+
|
|
203
|
+
Security/IoMethods:
|
|
204
|
+
Enabled: true
|
|
205
|
+
|
|
206
|
+
Security/YAMLLoad:
|
|
207
|
+
Enabled: false
|
|
208
|
+
|
|
209
|
+
Style/AccessorGrouping:
|
|
210
|
+
EnforcedStyle: separated
|
|
211
|
+
|
|
212
|
+
Style/ArgumentsForwarding:
|
|
213
|
+
Enabled: false
|
|
214
|
+
|
|
215
|
+
Style/ArrayIntersect:
|
|
216
|
+
Enabled: true
|
|
217
|
+
|
|
218
|
+
Style/AutoResourceCleanup:
|
|
219
|
+
Enabled: true
|
|
220
|
+
|
|
221
|
+
Style/CollectionCompact:
|
|
222
|
+
Enabled: true
|
|
223
|
+
|
|
224
|
+
Style/CollectionMethods:
|
|
225
|
+
Enabled: true
|
|
226
|
+
|
|
227
|
+
Style/ComparableClamp:
|
|
228
|
+
Enabled: true
|
|
229
|
+
|
|
230
|
+
Style/ConcatArrayLiterals:
|
|
231
|
+
Enabled: true
|
|
232
|
+
|
|
233
|
+
Style/DirEmpty:
|
|
234
|
+
Enabled: true
|
|
235
|
+
|
|
236
|
+
Style/DocumentDynamicEvalDefinition:
|
|
237
|
+
Enabled: false
|
|
238
|
+
|
|
239
|
+
Style/Documentation:
|
|
240
|
+
Enabled: false
|
|
241
|
+
|
|
242
|
+
Style/DocumentationMethod:
|
|
243
|
+
Enabled: false
|
|
244
|
+
|
|
245
|
+
Style/DoubleNegation:
|
|
246
|
+
EnforcedStyle: forbidden
|
|
247
|
+
|
|
248
|
+
Style/EmptyHeredoc:
|
|
249
|
+
Enabled: true
|
|
250
|
+
|
|
251
|
+
Style/EmptyMethod:
|
|
252
|
+
EnforcedStyle: expanded
|
|
253
|
+
|
|
254
|
+
Style/EndlessMethod:
|
|
255
|
+
Enabled: true
|
|
256
|
+
EnforcedStyle: disallow
|
|
257
|
+
|
|
258
|
+
Style/EnvHome:
|
|
259
|
+
Enabled: true
|
|
260
|
+
|
|
261
|
+
Style/FetchEnvVar:
|
|
262
|
+
Enabled: false
|
|
263
|
+
|
|
264
|
+
Style/FileEmpty:
|
|
265
|
+
Enabled: true
|
|
266
|
+
|
|
267
|
+
Style/FileRead:
|
|
268
|
+
Enabled: true
|
|
269
|
+
|
|
270
|
+
Style/FileWrite:
|
|
271
|
+
Enabled: true
|
|
272
|
+
|
|
273
|
+
Style/FormatString:
|
|
274
|
+
EnforcedStyle: percent
|
|
275
|
+
|
|
276
|
+
Style/FrozenStringLiteralComment:
|
|
277
|
+
Enabled: false
|
|
278
|
+
|
|
279
|
+
Style/HashConversion:
|
|
280
|
+
Enabled: true
|
|
281
|
+
|
|
282
|
+
Style/HashExcept:
|
|
283
|
+
Enabled: true
|
|
284
|
+
|
|
285
|
+
Style/HashSyntax:
|
|
286
|
+
EnforcedShorthandSyntax: never
|
|
287
|
+
|
|
288
|
+
Style/IfWithBooleanLiteralBranches:
|
|
289
|
+
Enabled: true
|
|
290
|
+
|
|
291
|
+
Style/ImplicitRuntimeError:
|
|
292
|
+
Enabled: true
|
|
293
|
+
|
|
294
|
+
Style/InPatternThen:
|
|
295
|
+
Enabled: true
|
|
296
|
+
|
|
297
|
+
Style/IpAddresses:
|
|
298
|
+
Enabled: true
|
|
299
|
+
|
|
300
|
+
Style/MagicCommentFormat:
|
|
301
|
+
Enabled: true
|
|
302
|
+
|
|
303
|
+
Style/MapCompactWithConditionalBlock:
|
|
304
|
+
Enabled: true
|
|
305
|
+
|
|
306
|
+
Style/MapToHash:
|
|
307
|
+
Enabled: true
|
|
308
|
+
|
|
309
|
+
Style/MapToSet:
|
|
310
|
+
Enabled: true
|
|
311
|
+
|
|
312
|
+
Style/MethodCallWithArgsParentheses:
|
|
313
|
+
Enabled: true
|
|
314
|
+
EnforcedStyle: omit_parentheses
|
|
315
|
+
AllowParenthesesInMultilineCall: true
|
|
316
|
+
AllowParenthesesInChaining: true
|
|
317
|
+
AllowParenthesesInCamelCaseMethod: true
|
|
318
|
+
|
|
319
|
+
Style/MethodDefParentheses:
|
|
320
|
+
EnforcedStyle: require_no_parentheses_except_multiline
|
|
321
|
+
|
|
322
|
+
Style/MinMaxComparison:
|
|
323
|
+
Enabled: true
|
|
324
|
+
|
|
325
|
+
Style/MultilineBlockChain:
|
|
326
|
+
Enabled: false
|
|
327
|
+
|
|
328
|
+
Style/MultilineInPatternThen:
|
|
329
|
+
Enabled: true
|
|
330
|
+
|
|
331
|
+
Style/NegatedIfElseCondition:
|
|
332
|
+
Enabled: true
|
|
333
|
+
|
|
334
|
+
Style/NestedFileDirname:
|
|
335
|
+
Enabled: true
|
|
336
|
+
|
|
337
|
+
Style/NestedParenthesizedCalls:
|
|
338
|
+
Enabled: false
|
|
339
|
+
|
|
340
|
+
Style/NilLambda:
|
|
341
|
+
Enabled: true
|
|
342
|
+
|
|
343
|
+
Style/NonNilCheck:
|
|
344
|
+
Enabled: false
|
|
345
|
+
|
|
346
|
+
Style/NumberedParameters:
|
|
347
|
+
Enabled: true
|
|
348
|
+
EnforcedStyle: disallow
|
|
349
|
+
|
|
350
|
+
Style/NumberedParametersLimit:
|
|
351
|
+
Enabled: true
|
|
352
|
+
|
|
353
|
+
Style/ObjectThen:
|
|
354
|
+
Enabled: true
|
|
355
|
+
|
|
356
|
+
Style/OpenStructUse:
|
|
357
|
+
Enabled: false
|
|
358
|
+
|
|
359
|
+
Style/OperatorMethodCall:
|
|
360
|
+
Enabled: true
|
|
361
|
+
|
|
362
|
+
Style/OptionHash:
|
|
363
|
+
Enabled: true
|
|
364
|
+
|
|
365
|
+
Style/QuotedSymbols:
|
|
366
|
+
Enabled: true
|
|
367
|
+
|
|
368
|
+
Style/RedundantArgument:
|
|
369
|
+
Enabled: false
|
|
370
|
+
|
|
371
|
+
Style/RedundantConstantBase:
|
|
372
|
+
Enabled: false
|
|
373
|
+
|
|
374
|
+
Style/RedundantDoubleSplatHashBraces:
|
|
375
|
+
Enabled: true
|
|
376
|
+
|
|
377
|
+
Style/RedundantEach:
|
|
378
|
+
Enabled: true
|
|
379
|
+
|
|
380
|
+
Style/RedundantException:
|
|
381
|
+
Enabled: false
|
|
382
|
+
|
|
383
|
+
Style/RedundantHeredocDelimiterQuotes:
|
|
384
|
+
Enabled: true
|
|
385
|
+
|
|
386
|
+
Style/RedundantInitialize:
|
|
387
|
+
Enabled: true
|
|
388
|
+
|
|
389
|
+
Style/RedundantParentheses:
|
|
390
|
+
Enabled: false
|
|
391
|
+
|
|
392
|
+
Style/RedundantSelfAssignmentBranch:
|
|
393
|
+
Enabled: true
|
|
394
|
+
|
|
395
|
+
Style/RedundantStringEscape:
|
|
396
|
+
Enabled: true
|
|
397
|
+
|
|
398
|
+
Style/RegexpLiteral:
|
|
399
|
+
EnforcedStyle: percent_r
|
|
400
|
+
|
|
401
|
+
Style/ReturnNil:
|
|
402
|
+
Enabled: true
|
|
403
|
+
|
|
404
|
+
Style/SelectByRegexp:
|
|
405
|
+
Enabled: true
|
|
406
|
+
|
|
407
|
+
Style/SingleLineMethods:
|
|
408
|
+
AllowIfMethodIsEmpty: false
|
|
409
|
+
|
|
410
|
+
Style/StaticClass:
|
|
411
|
+
Enabled: true
|
|
412
|
+
|
|
413
|
+
Style/StringChars:
|
|
414
|
+
Enabled: true
|
|
415
|
+
|
|
416
|
+
Style/StringHashKeys:
|
|
417
|
+
Enabled: false
|
|
418
|
+
|
|
419
|
+
Style/SwapValues:
|
|
420
|
+
Enabled: true
|
|
421
|
+
|
|
422
|
+
Style/SymbolArray:
|
|
423
|
+
EnforcedStyle: brackets
|
|
424
|
+
|
|
425
|
+
Style/TernaryParentheses:
|
|
426
|
+
EnforcedStyle: require_parentheses_when_complex
|
|
427
|
+
AllowSafeAssignment: false
|
|
428
|
+
|
|
429
|
+
Style/TopLevelMethodDefinition:
|
|
430
|
+
Enabled: true
|
|
431
|
+
|
|
432
|
+
Style/TrailingCommaInArguments:
|
|
433
|
+
Enabled: true
|
|
434
|
+
EnforcedStyleForMultiline: no_comma
|
|
435
|
+
|
|
436
|
+
Style/TrailingCommaInArrayLiteral:
|
|
437
|
+
Enabled: true
|
|
438
|
+
EnforcedStyleForMultiline: no_comma
|
|
439
|
+
|
|
440
|
+
Style/TrailingCommaInBlockArgs:
|
|
441
|
+
Enabled: true
|
|
442
|
+
|
|
443
|
+
Style/TrailingCommaInHashLiteral:
|
|
444
|
+
Enabled: true
|
|
445
|
+
EnforcedStyleForMultiline: no_comma
|
|
446
|
+
|
|
447
|
+
Style/UnlessLogicalOperators:
|
|
448
|
+
EnforcedStyle: forbid_logical_operators
|
|
449
|
+
|
|
450
|
+
Style/WordArray:
|
|
451
|
+
EnforcedStyle: brackets
|
|
452
|
+
|
|
453
|
+
Style/YodaCondition:
|
|
454
|
+
Enabled: true
|
|
455
|
+
EnforcedStyle: forbid_for_all_comparison_operators
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
<!--[//]: # (
|
|
4
|
+
## <Release number> <Date YYYY-MM-DD>
|
|
5
|
+
### Breaking changes
|
|
6
|
+
### Deprecations
|
|
7
|
+
### New features
|
|
8
|
+
### Bug fixes
|
|
9
|
+
)-->
|
|
10
|
+
|
|
11
|
+
## 1.0.0 2024-09-09
|
|
12
|
+
|
|
13
|
+
First public release. Refer to [README.md](README.md) for the full documentation.
|
data/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Json Path RFC 9535
|
|
2
|
+
|
|
3
|
+
A Ruby implementation of RFC 9535.
|
|
4
|
+
|
|
5
|
+
Like XPath is a query language for XML, JsonPath is a query language for JSON. This gem aims to be an implementation of RFC 9535. Unlike tha original JsonPath description (http://goessner.net/articles/JsonPath/), RFC 9535 is strictly normative, which ideally should leave open fewer doors for inconsistencies.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'json_path_rfc9535', '~> 1.0'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
And then execute:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
$ bundle
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or you can install the gem on its own:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
gem install json_path_rfc9535
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
Parse the Json into a `JsonPath::Doc` instance...
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
doc = JsonPath::Doc(<<~JSON)
|
|
33
|
+
{
|
|
34
|
+
"store": {
|
|
35
|
+
"book": [
|
|
36
|
+
{
|
|
37
|
+
"category": "reference",
|
|
38
|
+
"author": "Nigel Rees",
|
|
39
|
+
"title": "Sayings of the Century",
|
|
40
|
+
"price": 8.95
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"category": "fiction",
|
|
44
|
+
"author": "Evelyn Waugh",
|
|
45
|
+
"title": "Sword of Honour",
|
|
46
|
+
"price": 12.99
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"category": "fiction",
|
|
50
|
+
"author": "Herman Melville",
|
|
51
|
+
"title": "Moby Dick",
|
|
52
|
+
"isbn": "0-553-21311-3",
|
|
53
|
+
"price": 8.99
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"category": "fiction",
|
|
57
|
+
"author": "J. R. R. Tolkien",
|
|
58
|
+
"title": "The Lord of the Rings",
|
|
59
|
+
"isbn": "0-395-19395-8",
|
|
60
|
+
"price": 22.99
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"bicycle": {
|
|
64
|
+
"color": "red",
|
|
65
|
+
"price": 399
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
JSON
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
... and then query it.
|
|
73
|
+
|
|
74
|
+
```ruby
|
|
75
|
+
doc.query('$.store.bicycle.color')
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The returned object has methods to retrieve the values or the paths of all the retrieved nodes:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
doc.query('$.store.book.*.category').values
|
|
82
|
+
# => ["reference", "fiction", "fiction", "fiction"]
|
|
83
|
+
doc.query('$.store.book.*.category').paths
|
|
84
|
+
# => ["$['store']['book'][0]['category']", "$['store']['book'][1]['category']", "$['store']['book'][2]['category']", "$['store']['book'][3]['category']"]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
You can also query it further:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
results = doc.query('$.store.book[?(@.price > 10)]')
|
|
91
|
+
results.paths
|
|
92
|
+
# => ["$['store']['book'][1]", "$['store']['book'][3]"]
|
|
93
|
+
results.query('$.author').values
|
|
94
|
+
# => ["Evelyn Waugh", "J. R. R. Tolkien"]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
This gem implements most of RFC 9535, with the exception of [function extensions](https://datatracker.ietf.org/doc/html/rfc9535#name-function-extensions) and the related [type system](https://datatracker.ietf.org/doc/html/rfc9535#name-type-system-for-function-ex). It also relies on the underlying Ruby interpreter for string evaluation, meaning that characters don't need to be double-escaped.
|
|
98
|
+
|
|
99
|
+
## Plans for future development
|
|
100
|
+
|
|
101
|
+
- Function extensions
|
|
102
|
+
- Function extensions type system
|
|
103
|
+
|
|
104
|
+
## Version numbers
|
|
105
|
+
|
|
106
|
+
Json Path RFC 9535 loosely follows [Semantic Versioning](https://semver.org/), with a hard guarantee that breaking changes to the public API will always coincide with an increase to the `MAJOR` number.
|
|
107
|
+
|
|
108
|
+
Version numbers are in three parts: `MAJOR.MINOR.PATCH`.
|
|
109
|
+
|
|
110
|
+
- Breaking changes to the public API increment the `MAJOR`. There may also be changes that would otherwise increase the `MINOR` or the `PATCH`.
|
|
111
|
+
- Additions, deprecations, and "big" non breaking changes to the public API increment the `MINOR`. There may also be changes that would otherwise increase the `PATCH`.
|
|
112
|
+
- Bug fixes and "small" non breaking changes to the public API increment the `PATCH`.
|
|
113
|
+
|
|
114
|
+
Notice that any feature deprecated by a minor release can be expected to be removed by the next major release.
|
|
115
|
+
|
|
116
|
+
## Changelog
|
|
117
|
+
|
|
118
|
+
Full list of changes in [CHANGELOG.md](CHANGELOG.md)
|
|
119
|
+
|
|
120
|
+
## Contributing
|
|
121
|
+
|
|
122
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/moku-io/json_path_rfc9535.
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require_relative 'nodes'
|
|
3
|
+
require_relative 'path'
|
|
4
|
+
require_relative 'node_list'
|
|
5
|
+
|
|
6
|
+
module JsonPath
|
|
7
|
+
class Doc
|
|
8
|
+
attr_reader :root_node
|
|
9
|
+
|
|
10
|
+
def initialize json_string, parse_json: json_string.is_a?(String)
|
|
11
|
+
json = (parse_json ? JSON.parse(json_string) : json_string)
|
|
12
|
+
@root_node = Nodes.parse '$', json
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def query json_path
|
|
16
|
+
json_path = Path.new json_path
|
|
17
|
+
|
|
18
|
+
json_path
|
|
19
|
+
.apply(root_node)
|
|
20
|
+
.then { NodeList.new _1 }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def value
|
|
24
|
+
root_node.value
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require_relative 'path'
|
|
2
|
+
|
|
3
|
+
module JsonPath
|
|
4
|
+
class NodeList
|
|
5
|
+
attr_reader :nodes
|
|
6
|
+
|
|
7
|
+
def initialize nodes
|
|
8
|
+
@nodes = nodes
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def query json_path
|
|
12
|
+
json_path = Path.new json_path
|
|
13
|
+
|
|
14
|
+
nodes
|
|
15
|
+
.flat_map { json_path.apply _1 }
|
|
16
|
+
.then { self.class.new _1 }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def values
|
|
20
|
+
nodes.map(&:value)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def paths
|
|
24
|
+
nodes.map(&:path)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module JsonPath
|
|
2
|
+
module Nodes
|
|
3
|
+
class Array < Base
|
|
4
|
+
alias elements children
|
|
5
|
+
|
|
6
|
+
def initialize path, values
|
|
7
|
+
super path
|
|
8
|
+
@children = values
|
|
9
|
+
.map.with_index do |value, i|
|
|
10
|
+
Nodes.parse "#{path}[#{i}]", value
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def value
|
|
15
|
+
elements.map(&:value)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module JsonPath
|
|
2
|
+
module Nodes
|
|
3
|
+
class Object < Base
|
|
4
|
+
attr_reader :hash
|
|
5
|
+
|
|
6
|
+
def initialize path, hash
|
|
7
|
+
super path
|
|
8
|
+
@hash = hash.to_h do |key, value|
|
|
9
|
+
[key, Nodes.parse("#{path}['#{key}']", value)]
|
|
10
|
+
end
|
|
11
|
+
@children = self.hash.values
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def value
|
|
15
|
+
hash.transform_values(&:value)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require_relative 'nodes/base'
|
|
2
|
+
Dir["#{__dir__}/nodes/**"].each { |filename| require_relative filename }
|
|
3
|
+
|
|
4
|
+
module JsonPath
|
|
5
|
+
module Nodes
|
|
6
|
+
def self.parse path, value
|
|
7
|
+
case value
|
|
8
|
+
when nil
|
|
9
|
+
Null.new path
|
|
10
|
+
when true
|
|
11
|
+
True.new path
|
|
12
|
+
when false
|
|
13
|
+
False.new path
|
|
14
|
+
when ::String
|
|
15
|
+
String.new path, value
|
|
16
|
+
when Numeric
|
|
17
|
+
Number.new path, value
|
|
18
|
+
when ::Array
|
|
19
|
+
Array.new path, value
|
|
20
|
+
when Hash
|
|
21
|
+
Object.new path, value
|
|
22
|
+
else
|
|
23
|
+
raise UnrecognizedNode, "JSON value expected, #{value.class} found"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require 'parslet'
|
|
2
|
+
|
|
3
|
+
module JsonPath
|
|
4
|
+
module Parser
|
|
5
|
+
class Core < Parslet::Parser
|
|
6
|
+
# Never matches
|
|
7
|
+
rule(:none) { match('').absent? }
|
|
8
|
+
# Always matches, but doesn't consume any input
|
|
9
|
+
rule(:empty) { str('') }
|
|
10
|
+
|
|
11
|
+
def many parser
|
|
12
|
+
parser.repeat
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def some parser
|
|
16
|
+
parser.repeat 1
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
rule(:digit) { match('[0-9]') }
|
|
20
|
+
rule(:digits) { digit.repeat(1) }
|
|
21
|
+
rule(:double_quoted_string_char) { match('[^\\\\"]') | str('\\\\') | str('\\"') }
|
|
22
|
+
rule(:single_quoted_string_char) { match("[^\\\\']") | str('\\\\') | str("\\'") }
|
|
23
|
+
rule(:whitespace) { match('[\s]').repeat }
|
|
24
|
+
|
|
25
|
+
rule(:int) { str('-').maybe >> digits }
|
|
26
|
+
rule :num do
|
|
27
|
+
int >>
|
|
28
|
+
(str('.') >> digits).maybe >>
|
|
29
|
+
(str('e') >> (str('-') | str('+')).maybe >> digits).maybe
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
rule(:double_quoted_string) { token(str('"') >> many(double_quoted_string_char).as(:string) >> str('"')) }
|
|
33
|
+
rule(:single_quoted_string) { token(str("'") >> many(single_quoted_string_char).as(:string) >> str("'")) }
|
|
34
|
+
|
|
35
|
+
rule(:true_constant) { symbol('true') }
|
|
36
|
+
rule(:false_constant) { symbol('false') }
|
|
37
|
+
rule(:integer) { token(int) }
|
|
38
|
+
rule(:number) { token(num) }
|
|
39
|
+
rule(:string) { double_quoted_string | single_quoted_string }
|
|
40
|
+
|
|
41
|
+
def symbol string
|
|
42
|
+
token(str(string))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def many_separated parser, separator_parser
|
|
46
|
+
some_separated(parser, separator_parser).maybe
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def some_separated parser, separator_parser
|
|
50
|
+
parser >> many(separator_parser >> parser)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def any_unless parser
|
|
54
|
+
parser.absent? >> any
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def any_until parser, prefix_name=nil
|
|
58
|
+
prefix_parser = some(any_unless(parser))
|
|
59
|
+
prefix_parser = prefix_parser.as(prefix_name) if prefix_name.present?
|
|
60
|
+
prefix_parser
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
protected
|
|
64
|
+
|
|
65
|
+
# Takes a block which needs to be a predicate over strings
|
|
66
|
+
def predicate parser
|
|
67
|
+
parser.capture(:input) >> dynamic { |_, c| yield(c.captures[:input].to_s) ? empty : none }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def token parser
|
|
71
|
+
whitespace.maybe >> parser >> whitespace
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require_relative 'core'
|
|
2
|
+
|
|
3
|
+
module JsonPath
|
|
4
|
+
module Parser
|
|
5
|
+
class RawParser < Core
|
|
6
|
+
rule(:jsonpath_query) { root_identifier >> many(segment).as(:segments) }
|
|
7
|
+
root :jsonpath_query
|
|
8
|
+
|
|
9
|
+
rule(:root_identifier) { str('$') }
|
|
10
|
+
rule(:current_node_identifier) { str('@') }
|
|
11
|
+
|
|
12
|
+
# Selectors
|
|
13
|
+
rule(:selector) { name_selector | wildcard_selector | slice_selector | index_selector | filter_selector }
|
|
14
|
+
rule(:name_selector) { string.as(:name) }
|
|
15
|
+
rule(:wildcard_selector) { str('*').as(:wildcard) }
|
|
16
|
+
rule(:index_selector) { int.as(:index) }
|
|
17
|
+
rule :slice_selector do
|
|
18
|
+
integer.maybe.as(:start) >>
|
|
19
|
+
str(':') >> whitespace >>
|
|
20
|
+
integer.maybe.as(:end) >>
|
|
21
|
+
(str(':') >> whitespace >> integer.maybe.as(:step)).maybe
|
|
22
|
+
end
|
|
23
|
+
rule(:filter_selector) { str('?') >> whitespace >> logical_expr.as(:filter) }
|
|
24
|
+
|
|
25
|
+
# Logical expressions
|
|
26
|
+
rule(:logical_expr) { logical_or_expr }
|
|
27
|
+
rule(:logical_or_expr) { some_separated(logical_and_expr.as(:logical_or_operands), symbol('||')) }
|
|
28
|
+
rule(:logical_and_expr) { some_separated(basic_expr.as(:logical_and_operands), symbol('&&')) }
|
|
29
|
+
rule(:basic_expr) { paren_expr | comparison_expr | test_expr }
|
|
30
|
+
rule(:logical_not_op) { symbol('!') }
|
|
31
|
+
rule :paren_expr do
|
|
32
|
+
logical_not_op.maybe.as(:negation) >> symbol('(') >> logical_expr.as(:parenthesized_expr) >> symbol(')')
|
|
33
|
+
end
|
|
34
|
+
rule(:test_expr) { logical_not_op.maybe.as(:negation) >> (filter_query | function_expr).as(:test_expr) }
|
|
35
|
+
rule(:filter_query) { (rel_query | jsonpath_query).as(:filter_query) }
|
|
36
|
+
rule(:rel_query) { current_node_identifier >> many(segment).as(:relative_segments) }
|
|
37
|
+
rule :comparison_expr do
|
|
38
|
+
comparable.as(:comparable1) >> comparison_op.as(:comparison_op) >> comparable.as(:comparable2)
|
|
39
|
+
end
|
|
40
|
+
rule(:comparison_op) { symbol('==') | symbol('!=') | symbol('<=') | symbol('>=') | symbol('<') | symbol('>') }
|
|
41
|
+
rule(:comparable) { literal | singular_query | function_expr }
|
|
42
|
+
rule :literal do
|
|
43
|
+
number.as(:literal_number) |
|
|
44
|
+
string.as(:literal_string) |
|
|
45
|
+
symbol('true').as(:literal_true) |
|
|
46
|
+
symbol('false').as(:literal_false) |
|
|
47
|
+
symbol('null').as(:literal_null)
|
|
48
|
+
end
|
|
49
|
+
rule(:singular_query) { (rel_singular_query | abs_singular_query).as(:singular_query) }
|
|
50
|
+
rule :rel_singular_query do
|
|
51
|
+
current_node_identifier >> many(singular_query_segment).as(:relative_segments)
|
|
52
|
+
end
|
|
53
|
+
rule :abs_singular_query do
|
|
54
|
+
root_identifier >> many(singular_query_segment).as(:segments)
|
|
55
|
+
end
|
|
56
|
+
rule(:singular_query_segment) { name_segment | index_segment }
|
|
57
|
+
rule(:name_segment) { ((str('[') >> name_selector >> str(']')) | (str('.') >> member_name_shorthand)).as(:child) }
|
|
58
|
+
rule(:index_segment) { (str('[') >> index_selector >> str(']')).as(:child) }
|
|
59
|
+
rule(:function_expr) { none }
|
|
60
|
+
|
|
61
|
+
# Segments
|
|
62
|
+
rule(:segment) { child_segment | descendant_segment }
|
|
63
|
+
rule :child_segment do
|
|
64
|
+
(bracketed_selection | (str('.') >> (wildcard_selector | member_name_shorthand))).as :child
|
|
65
|
+
end
|
|
66
|
+
rule(:bracketed_selection) { str('[') >> some_separated(selector, symbol(',')).as(:bracketed) >> str(']') }
|
|
67
|
+
rule(:member_name_shorthand) { (match('[a-zA-Z_]') >> many(match('[a-zA-Z_0-9]'))).as(:name) }
|
|
68
|
+
rule :descendant_segment do
|
|
69
|
+
(str('..') >> (bracketed_selection | wildcard_selector | member_name_shorthand)).as :descendant
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
require 'parslet'
|
|
2
|
+
require_relative '../multiple_values_returned_by_singular_query'
|
|
3
|
+
|
|
4
|
+
module JsonPath
|
|
5
|
+
module Parser
|
|
6
|
+
class Transformer < Parslet::Transform
|
|
7
|
+
EMPTY = ::Object.new
|
|
8
|
+
|
|
9
|
+
rule segments: sequence(:segments) do
|
|
10
|
+
proc do |root|
|
|
11
|
+
segments.reduce [root] do |nodes, segment|
|
|
12
|
+
segment.call nodes, root
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
rule(child: simple(:segment)) { segment }
|
|
18
|
+
|
|
19
|
+
rule descendant: simple(:segment) do
|
|
20
|
+
proc do |nodes, root|
|
|
21
|
+
recursive_application = proc do |node|
|
|
22
|
+
segment.call([node], root) + node.children.flat_map(&recursive_application)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
nodes.flat_map(&recursive_application)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
rule(bracketed: simple(:segment)) { segment }
|
|
30
|
+
|
|
31
|
+
rule bracketed: sequence(:segments) do
|
|
32
|
+
proc do |nodes, root|
|
|
33
|
+
segments.flat_map { _1.call nodes, root }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
rule name: simple(:name) do
|
|
38
|
+
proc do |nodes|
|
|
39
|
+
nodes.filter_map do |node|
|
|
40
|
+
node.hash[name.to_s] if node.is_a? Nodes::Object
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
rule wildcard: simple(:x) do
|
|
46
|
+
proc do |nodes|
|
|
47
|
+
nodes.flat_map(&:children)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
rule index: simple(:index) do
|
|
52
|
+
proc do |nodes|
|
|
53
|
+
nodes.filter_map do |node|
|
|
54
|
+
node.elements[Integer(index)] if node.is_a? Nodes::Array
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
rule start: simple(:slice_start),
|
|
60
|
+
end: simple(:slice_end),
|
|
61
|
+
step: simple(:slice_step) do
|
|
62
|
+
proc do |nodes|
|
|
63
|
+
slice_step = Integer(self.slice_step) || 1
|
|
64
|
+
|
|
65
|
+
next [] if slice_step.zero?
|
|
66
|
+
|
|
67
|
+
nodes.flat_map do |node|
|
|
68
|
+
next [] unless node.is_a? Nodes::Array
|
|
69
|
+
|
|
70
|
+
len = node.elements.size
|
|
71
|
+
|
|
72
|
+
slice_start, slice_end = if slice_step.positive?
|
|
73
|
+
[
|
|
74
|
+
Integer(self.slice_start) || 0,
|
|
75
|
+
Integer(self.slice_end) || len
|
|
76
|
+
]
|
|
77
|
+
else
|
|
78
|
+
[
|
|
79
|
+
Integer(self.slice_start) || (len - 1),
|
|
80
|
+
Integer(self.slice_end) || (-len - 1)
|
|
81
|
+
]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
slice_start += len if slice_start.negative?
|
|
85
|
+
slice_end += len if slice_end.negative?
|
|
86
|
+
|
|
87
|
+
lower, upper = if slice_step.positive?
|
|
88
|
+
[
|
|
89
|
+
[[slice_start, 0].max, len].min,
|
|
90
|
+
[[slice_end, 0].max, len].min
|
|
91
|
+
]
|
|
92
|
+
else
|
|
93
|
+
[
|
|
94
|
+
[[slice_end, -1].max, len - 1].min,
|
|
95
|
+
[[slice_start, -1].max, len - 1].min
|
|
96
|
+
]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# This is horrible, but it's also the easiest way to implement the semantics from the RFC
|
|
100
|
+
[].tap do |result|
|
|
101
|
+
if slice_step.positive?
|
|
102
|
+
i = lower
|
|
103
|
+
while i < upper
|
|
104
|
+
result << node.elements[i] if (0...node.elements.size).include? i
|
|
105
|
+
i += slice_step
|
|
106
|
+
end
|
|
107
|
+
else
|
|
108
|
+
i = upper
|
|
109
|
+
while i > lower
|
|
110
|
+
result << node.elements[i] if (0...node.elements.size).include? i
|
|
111
|
+
i += step
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
rule filter: simple(:filter) do
|
|
120
|
+
proc do |nodes, root|
|
|
121
|
+
nodes.flat_map do |node|
|
|
122
|
+
case node
|
|
123
|
+
when Nodes::Array
|
|
124
|
+
node.elements
|
|
125
|
+
when Nodes::Object
|
|
126
|
+
node.hash.values
|
|
127
|
+
else
|
|
128
|
+
[]
|
|
129
|
+
end
|
|
130
|
+
.filter { filter.call root, _1 }
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
rule(logical_or_operands: simple(:operand)) { operand }
|
|
136
|
+
rule(logical_and_operands: simple(:operand)) { operand }
|
|
137
|
+
|
|
138
|
+
rule logical_or_operands: sequence(:operands) do
|
|
139
|
+
proc do |*args, **kwargs, &block|
|
|
140
|
+
operands.any? { _1.call(*args, **kwargs, &block) }
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
rule logical_and_operands: sequence(:operands) do
|
|
145
|
+
proc do |*args, **kwargs, &block|
|
|
146
|
+
operands.all? { _1.call(*args, **kwargs, &block) }
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
rule negation: simple(:negation),
|
|
151
|
+
test_expr: simple(:expr) do
|
|
152
|
+
expr >> proc { negation ? !_1 : _1 }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
rule filter_query: simple(:query) do
|
|
156
|
+
query >> proc { !_1.empty? }
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
rule negation: simple(:negation),
|
|
160
|
+
parenthesized_expr: simple(:expr) do
|
|
161
|
+
expr >> proc { negation ? !_1 : _1 }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
rule comparable1: simple(:comp1),
|
|
165
|
+
comparison_op: simple(:op),
|
|
166
|
+
comparable2: simple(:comp2) do
|
|
167
|
+
proc do |*args, **kwargs, &block|
|
|
168
|
+
[comp1.call(*args, **kwargs, &block), comp2.call(*args, **kwargs, &block)]
|
|
169
|
+
end >> proc { _1.public_send op.to_s.strip.to_sym, _2 }
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
rule relative_segments: sequence(:segments) do
|
|
173
|
+
proc do |root, current_node|
|
|
174
|
+
segments.reduce [current_node] do |nodes, segment|
|
|
175
|
+
segment.call nodes, root
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
rule literal_number: simple(:num) do
|
|
181
|
+
proc { eval num }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
rule literal_string: simple(:string) do
|
|
185
|
+
proc { string.to_s }
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
rule literal_true: simple(:x) do
|
|
189
|
+
proc { true }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
rule literal_false: simple(:x) do
|
|
193
|
+
proc { false }
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
rule literal_null: simple(:x) do
|
|
197
|
+
proc {}
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
rule string: simple(:string) do
|
|
201
|
+
string
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
rule singular_query: simple(:query) do
|
|
205
|
+
proc do |*args, **kwargs, &block|
|
|
206
|
+
nodes = query.call(*args, **kwargs, &block)
|
|
207
|
+
|
|
208
|
+
if nodes.empty?
|
|
209
|
+
EMPTY
|
|
210
|
+
elsif nodes.size == 1
|
|
211
|
+
nodes.first.value
|
|
212
|
+
else
|
|
213
|
+
raise MultipleValuesReturnedBySingularQuery, 'Singular query must return single value'
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require_relative 'parser/raw_parser'
|
|
2
|
+
require_relative 'parser/transformer'
|
|
3
|
+
|
|
4
|
+
module JsonPath
|
|
5
|
+
module Parser
|
|
6
|
+
RAW_PARSER = RawParser.new
|
|
7
|
+
TRANSFORMER = Transformer.new
|
|
8
|
+
|
|
9
|
+
def self.compile json_path_string
|
|
10
|
+
reporter = Parslet::ErrorReporter::Contextual.new
|
|
11
|
+
TRANSFORMER.apply RAW_PARSER.parse(json_path_string, reporter: reporter)
|
|
12
|
+
rescue Parslet::ParseFailed
|
|
13
|
+
reporter.deepest_cause.raise
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require_relative 'parser'
|
|
2
|
+
|
|
3
|
+
module JsonPath
|
|
4
|
+
class Path
|
|
5
|
+
attr_reader :string
|
|
6
|
+
attr_reader :proc
|
|
7
|
+
|
|
8
|
+
def initialize json_path
|
|
9
|
+
if json_path.is_a? Path
|
|
10
|
+
@string = json_path.string
|
|
11
|
+
@proc = json_path.proc
|
|
12
|
+
else
|
|
13
|
+
@string = -json_path
|
|
14
|
+
@proc = Parser.compile json_path
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def apply node
|
|
19
|
+
proc.call node
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/json_path.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: json_path_rfc9535
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Moku S.r.l.
|
|
8
|
+
- Riccardo Agatea
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: exe
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2024-09-09 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: parslet
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
requirements:
|
|
18
|
+
- - "~>"
|
|
19
|
+
- !ruby/object:Gem::Version
|
|
20
|
+
version: '2.0'
|
|
21
|
+
type: :runtime
|
|
22
|
+
prerelease: false
|
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - "~>"
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
version: '2.0'
|
|
28
|
+
description: Like XPath is a query language for XML, JsonPath is a query language
|
|
29
|
+
for JSON. This gem aims to be an implementation of RFC 9535. Unlike tha original
|
|
30
|
+
JsonPath description (http://goessner.net/articles/JsonPath/), RFC 9535 is strictly
|
|
31
|
+
normative, which ideally should leave open fewer doors for inconsistencies.
|
|
32
|
+
email:
|
|
33
|
+
- info@moku.io
|
|
34
|
+
executables: []
|
|
35
|
+
extensions: []
|
|
36
|
+
extra_rdoc_files: []
|
|
37
|
+
files:
|
|
38
|
+
- ".rubocop.yml"
|
|
39
|
+
- CHANGELOG.md
|
|
40
|
+
- README.md
|
|
41
|
+
- lib/json_path.rb
|
|
42
|
+
- lib/json_path/doc.rb
|
|
43
|
+
- lib/json_path/error.rb
|
|
44
|
+
- lib/json_path/multiple_values_returned_by_singular_query.rb
|
|
45
|
+
- lib/json_path/node_list.rb
|
|
46
|
+
- lib/json_path/nodes.rb
|
|
47
|
+
- lib/json_path/nodes/array.rb
|
|
48
|
+
- lib/json_path/nodes/base.rb
|
|
49
|
+
- lib/json_path/nodes/false.rb
|
|
50
|
+
- lib/json_path/nodes/null.rb
|
|
51
|
+
- lib/json_path/nodes/number.rb
|
|
52
|
+
- lib/json_path/nodes/object.rb
|
|
53
|
+
- lib/json_path/nodes/string.rb
|
|
54
|
+
- lib/json_path/nodes/true.rb
|
|
55
|
+
- lib/json_path/parser.rb
|
|
56
|
+
- lib/json_path/parser/core.rb
|
|
57
|
+
- lib/json_path/parser/raw_parser.rb
|
|
58
|
+
- lib/json_path/parser/transformer.rb
|
|
59
|
+
- lib/json_path/path.rb
|
|
60
|
+
- lib/json_path/unrecognized_node.rb
|
|
61
|
+
- lib/json_path_rfc9535.rb
|
|
62
|
+
- lib/json_path_rfc9535/version.rb
|
|
63
|
+
homepage: https://github.com/moku-io/json_path_rfc9535
|
|
64
|
+
licenses:
|
|
65
|
+
- MIT
|
|
66
|
+
metadata:
|
|
67
|
+
homepage_uri: https://github.com/moku-io/json_path_rfc9535
|
|
68
|
+
source_code_uri: https://github.com/moku-io/json_path_rfc9535
|
|
69
|
+
changelog_uri: https://github.com/moku-io/json_path_rfc9535/blob/master/CHANGELOG.md
|
|
70
|
+
post_install_message:
|
|
71
|
+
rdoc_options: []
|
|
72
|
+
require_paths:
|
|
73
|
+
- lib
|
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: 3.0.0
|
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ">="
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: '0'
|
|
84
|
+
requirements: []
|
|
85
|
+
rubygems_version: 3.5.11
|
|
86
|
+
signing_key:
|
|
87
|
+
specification_version: 4
|
|
88
|
+
summary: A Ruby implementation of RFC 9535.
|
|
89
|
+
test_files: []
|