Skip to content

Commit cb43c0d

Browse files
authored
Merge branch 'main' into jep/object-manipulation-functions
2 parents 8e53ddc + 6351eb2 commit cb43c0d

22 files changed

+1519
-28
lines changed

GRAMMAR.ABNF

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
;; The result of applying a JMESPath expression against a JSON document will always result in valid JSON, provided there
2020
;; are no errors during the evaluation process. Structured data in, structured data out.
2121
;;
22-
;; This also means that, with the exception of JMESPath expression types, JMESPath only supports the same types support by JSON:
22+
;; This also means that, with the exception of JMESPath expression types, JMESPath only supports the same types supported by JSON:
2323
;;
2424
;; - number (integers and double-precision floating-point format in JSON)
2525
;; - string
@@ -62,7 +62,8 @@ sub-expression = expression "." ( identifier / multi-select-list / multi-sele
6262
;; In pseudocode:
6363
;; ```
6464
;; left-evaluation = search(left-expression, original-json-document)
65-
;; result = search(right-expression, left-evaluation)
65+
;; if left-evaluation is `null` then result = `null`
66+
;; else result = search(right-expression, left-evaluation)
6667
;; ```
6768
;; A sub-expression is itself an expression, so there can be multiple levels of sub-expressions: grandparent.parent.child.
6869
;; Examples
@@ -252,36 +253,36 @@ bracket-specifier =/ "[]" ;; ## Flatten Operator
252253
;; ```
253254

254255
slice-expression = [number] ":" [number] [ ":" [number] ] ;; ## Slices
255-
;; A slice expression allows you to select a subset of an array. A slice has a start, stop, and step value. The
256-
;; general form of a slice is [start:stop:step], but each component is optional and can be omitted.
256+
;; A slice expression allows you to select a subset of an array or a string. A slice has a `start`, `stop`, and `step` value. The
257+
;; general form of a slice is `[start:stop:step]`, but each component is optional and can be omitted.
257258
;;
258259
;; ```note
259260
;; Slices in JMESPath have the same semantics as python slices. If you're familiar with python slices, you're familiar with
260261
;; JMESPath slices.
261262
;; ```
262263
;;
263-
;; Given a start, stop, and step value, the sub elements in an array are extracted as follows:
264+
;; Given a `start`, `stop`, and `step` value, the sub elements in an array or characters in a string are extracted as follows:
264265
;;
265-
;; - The first element in the extracted array is the index denoted by start.
266-
;; - The last element in the extracted array is the index denoted by end - 1.
267-
;; - The step value determines how many indices to skip after each element is selected from the array.
268-
;; The default step value of 1 will not skip any indices and will return a contiguous subset of the original array.
269-
;; A step value greater than 1 will skip indices while extracting elements from an array. For instance, a step value of 2 will
270-
;; skip every other index.
271-
;; Negative step values start from the end of the array and extract elements in reverse order.
266+
;; - The first element in the extracted array or first character in the extracted string is the index denoted by `start`.
267+
;; - The last element in the extracted array or last character in the extracted string is the index denoted by `end - 1`.
268+
;; - The `step` value determines how many indices to skip after each element is selected from the array or each character is selected from the string.
269+
;; The default `step` value of `1` will not skip any indices and will return a contiguous subset of the original array or a substring of the original string.
270+
;; A `step` value greater than `1` will skip indices while extracting elements from an array or characters from a string. For instance, a `step` value of `2` will
271+
;; skip every other element or character.
272+
;; Negative `step` values start from the end of the array or string and extract elements or characters in reverse order.
272273
;;
273274
;; Slice expressions adhere to the following rules:
274275
;;
275-
;; - If a negative start position is given, it is calculated as the total length of the array plus the given start position.
276-
;; - If no start position is given, it is assumed to be 0 if the given step is greater than 0 or the end of the array if
277-
;; the given step is less than 0.
278-
;; - If a negative stop position is given, it is calculated as the total length of the array plus the given stop position.
279-
;; - If no stop position is given, it is assumed to be the length of the array if the given step is greater than 0 or 0 if
280-
;; the given step is less than 0.
281-
;; - If the given step is omitted, it it assumed to be 1.
282-
;; - If the given step is 0, an error MUST be raised.
283-
;; - If the element being sliced is not an array, the result is null.
284-
;; - If the element being sliced is an array and yields no results, the result MUST be an empty array.
276+
;; - If a negative `start` position is given, it is calculated as the total length of the array or string plus the given `start` position.
277+
;; - If no `start` position is given, it is assumed to be `0` if the given `step` is greater than 0 or the end of the array or string if
278+
;; the given `step` is less than `0`.
279+
;; - If a negative `stop` position is given, it is calculated as the total length of the array or string plus the given `stop` position.
280+
;; - If no `stop` position is given, it is assumed to be the length of the array or string if the given `step` is greater than `0` or `0` if
281+
;; the given `step` is less than `0`.
282+
;; - If the given `step` is omitted, it it assumed to be `1`.
283+
;; - If the given `step` is `0`, an error MUST be raised.
284+
;; - If the element being sliced is not an array or a string, the result is `null`.
285+
;; - If the element being sliced is an array or string and yields no results, the result MUST be an empty array.
285286
;;
286287
;; ### Examples
287288
;; ```
@@ -293,6 +294,12 @@ slice-expression = [number] ":" [number] [ ":" [number] ] ;; ## Slices
293294
;; search([::-1], [0, 1, 2, 3]) -> [3, 2, 1, 0]
294295
;; search([-2:], [0, 1, 2, 3]) -> [2, 3]
295296
;; ```
297+
;; Slicing operates on strings exactly as if a string were thought of as an array of characters.
298+
;; ```
299+
;; search(foo[0:4], {"foo": "hello, world!"}) -> "hell"
300+
;; search([::], 'raw-string') -> "raw-string"
301+
;; search([::2], 'raw-string') -> "rwsrn"
302+
;; search([::-1], 'raw-string') -> "gnirts-war"
296303

297304
multi-select-list = "[" ( expression *( "," expression ) ) "]" ;; # MultiSelect List
298305
;; A multiselect expression is used to extract a subset of elements from a JSON hash. There are two version of multiselect,
@@ -601,7 +608,7 @@ unquoted-string = (%x41-5A / %x61-7A / %x5F) *( ; A-Za-z_
601608
%x5F / ; _
602609
%x61-7A) ; a-z
603610
quoted-string = quote 1*(unescaped-char / escaped-char) quote
604-
unescaped-char = %x20-21 / %x23-5B / %x5D-10FFFF
611+
unescaped-char = %x20-21 / %x23-2E / %30-5B / %x5D-10FFFF
605612
escape = "\"
606613
quote = %x22 ; Double quote: '"'
607614
escaped-char = escape (
@@ -650,3 +657,39 @@ int = zero / ( digit1-9 *digit )
650657
minus = %x2D ; -
651658
plus = %x2B ; +
652659
zero = %x30 ; 0
660+
661+
expression =/ root-node ;; ## Root Reference
662+
663+
root-node = "$"
664+
665+
;; ## root-node
666+
;;
667+
;; The `root-node` token can be used to represent the original input JSON document.
668+
;;
669+
;; As a JMESPath expression is being evaluated, the current scope changes.
670+
;; Given a simple sub expression such as `foo.bar`, first the `foo` expression is evaluated
671+
;; with the starting input JSON document, and the result of that expression is then used as
672+
;; the current scope when the `bar` element is evaluated.
673+
;;
674+
;; Once we’ve drilled down to a specific scope, the `root-node` token can be used
675+
;; to refer to the original JSON document.
676+
;;
677+
;; Example
678+
;;
679+
;; Given a JSON document:
680+
;;
681+
;; ```json
682+
;; {
683+
;; "first_choice": "WA",
684+
;; "states": [
685+
;; {"name": "WA", "cities": ["Seattle", "Bellevue", "Olympia"]},
686+
;; {"name": "CA", "cities": ["Los Angeles", "San Francisco"]},
687+
;; {"name": "NY", "cities": ["New York City", "Albany"]}
688+
;; ]
689+
;; }
690+
;; ```
691+
;; We can retrieve the list of cities of the state corresponding to the `first_choice` key
692+
;; using the following expression:
693+
;;
694+
;; ` states[? name == $.first_choice ].cities[] `
695+
;;

function_schema.yml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,17 @@ properties:
5151
type: array
5252
description: ""
5353
items:
54-
type: object
5554
allOf:
5655
- $ref: "#/$defs/unnamed_arg"
56+
- type: object
57+
required:
58+
- name
59+
properties:
60+
name:
61+
type: string
62+
description: ""
5763
description: ""
5864
unevaluatedProperties: false
59-
properties:
60-
name:
61-
type: string
62-
description: ""
6365
optional: *arg
6466
variadic: *unnamed_arg
6567
returns:

functions/find_first.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: find_first
2+
topic: strings
3+
args:
4+
required:
5+
- name: subject
6+
type: [string]
7+
desc: 'Subject string'
8+
- name: sub
9+
type: [string]
10+
desc: 'Substring'
11+
optional:
12+
- name: start
13+
type: [number]
14+
desc: 'Position in the subject string where searching should start.'
15+
- name: end
16+
type: [number]
17+
desc: 'Position in the subject string where searching should end.'
18+
returns:
19+
type: number
20+
desc: ''
21+
desc: |
22+
Given the `subject` string, `find_first()` returns the index of the first occurence where the `sub` substring appears in `subject` or `null`.
23+
24+
The `start` and `end` parameters are optional and allow to select where `find_first()` must perform its search within `subject`.
25+
26+
* If `start` is omitted, it defaults to `0` which is the start of the `subject` string.
27+
* If `end` is omitted, it defaults to `length(subject) - 1` which is is the end of the `subject` string.
28+
examples:
29+
find_first:
30+
context: &subject subject string
31+
args: ['@', 'string']
32+
returns: 8.0
33+
find_first_start:
34+
context: *subject
35+
args: ['@', 'string', '`0`']
36+
returns: 8.0
37+
find_first_start_end:
38+
context: *subject
39+
args: ['@', 'string', '`0`', '`14`']
40+
returns: 8.0
41+
find_first_start_before_end_after:
42+
context: *subject
43+
args: ['@', 'string', '`-99`', '`100`']
44+
returns: 8.0
45+
find_first_ends_prematurely:
46+
context: *subject
47+
args: ['@', 'string', '`0`', '`13`']
48+
returns: null
49+
find_first_start_boundary:
50+
context: *subject
51+
args: ['@', 'string', '`8`']
52+
returns: 8.0
53+
find_first_incomplete:
54+
context: *subject
55+
args: ['@', 'string', '`9`']
56+
returns: null
57+
find_first_first_occurrence:
58+
context: *subject
59+
args: ['@', 's', '`0`']
60+
returns: 0.0
61+
find_first_second_occurrence:
62+
context: *subject
63+
args: ['@', 's', '`1`']
64+
returns: 8.0

functions/find_last.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: find_last
2+
topic: strings
3+
args:
4+
required:
5+
- name: subject
6+
type: [string]
7+
desc: 'Subject string'
8+
- name: sub
9+
type: [string]
10+
desc: 'Substring'
11+
optional:
12+
- name: pos
13+
type: [number]
14+
desc: 'Position in the subject string where searching should start.'
15+
returns:
16+
type: number
17+
desc: ''
18+
desc: |
19+
Given the `subject` string, `find_last()` returns the index of the last occurence where the `sub` substring appears in `subject` or `null`.
20+
21+
The `pos` parameter is optional and allow to select where `find_first()` must perform its search within `subject`.
22+
If this is parameter omitted, it defaults to `length(subject) - 1` which is is the end of the `subject` string.
23+
`find_last()` then searches backwards in the `subject` string for the first occurrence of the `sub` substring.
24+
examples:
25+
find_last:
26+
context: &subject subject string
27+
args: ['@', 'string']
28+
returns: 8.0
29+
find_last_pos:
30+
context: *subject
31+
args: ['@', 'string', '`8`']
32+
returns: 8.0
33+
find_last_pos_ends_prematurely:
34+
context: *subject
35+
args: ['@', 'string', '`7`']
36+
returns: null
37+
find_last_second_occurrence:
38+
context: *subject
39+
args: ['@', 's', '`8`']
40+
returns: 8.0
41+
find_last_first_occurrence:
42+
context: *subject
43+
args: ['@', 's', '`7`']
44+
returns: 0.0

functions/group_by.yml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: group_by
2+
topic: collections
3+
args:
4+
required:
5+
- name: elements
6+
type: ['array[object]']
7+
desc: ''
8+
- name: expr
9+
type: [expression->string]
10+
desc: ''
11+
optional: []
12+
returns:
13+
type: object
14+
desc: ''
15+
desc: |
16+
Groups an array of objects `$elements` using an expression `$expr` as the group key.
17+
The `$expr` expression is applied to each element in the array `$elements` and the
18+
resulting value is used as a group key.
19+
20+
The result is an object whose keys are the unique set of string keys and whose respective values are an array of objects array matching the group criteria.
21+
22+
Objects that do not match the group criteria are discarded from the output.
23+
This includes objects for which applying the `$expr` expression evaluates to `null`.
24+
25+
If the result of applying the `$expr` expression against the current array element
26+
results in type other than `string` or `null`, a type error MUST be raised.
27+
examples:
28+
group_by:
29+
context:
30+
items:
31+
- spec:
32+
nodeName: node_01
33+
other: values_01
34+
- spec:
35+
nodeName: node_02
36+
other: values_02
37+
- spec:
38+
nodeName: node_03
39+
other: values_03
40+
- spec:
41+
nodeName: node_01
42+
other: values_04
43+
args: [items, '&nodeName']
44+
returns:
45+
nodeName:
46+
node_01:
47+
- spec:
48+
nodeName: node_01
49+
other: values_01
50+
- spec:
51+
nodeName: node_01
52+
other: values_04
53+
node_02:
54+
- spec:
55+
nodeName: node_02
56+
other: values_02
57+
node_03:
58+
- spec:
59+
nodeName: node_03
60+
other: values_03
61+
group_by_discards_null_keyed_objects:
62+
context: &data
63+
array:
64+
- b: true
65+
name: one
66+
- b: false
67+
name: two
68+
- b: false
69+
args: [array, '&name']
70+
returns: [{age: 10, age_str: '10', bool: true, name: 3}, {age: 20, age_str: '20',
71+
bool: true, name: a, extra: foo}, {age: 30, age_str: '30', bool: true, name: c},
72+
{age: 40, age_str: '40', bool: false, name: b, extra: bar}, {age: 50, age_str: '50',
73+
bool: false, name: d}]
74+
group_by_errs_invalid_type:
75+
context: *data
76+
args: [array, '&b']
77+
error: invalid-type

0 commit comments

Comments
 (0)