Nested Expressions

JEP

1

Author

Michael Dowling

Status

accepted

Created

27-Nov-2013

Abstract

This document proposes modifying the JMESPath grammar to support arbitrarily nested expressions within multi-select-list and multi-select-hash expressions.

Motivation

This JMESPath grammar currently does not allow arbitrarily nested expressions within multi-select-list and multi-select-hash expressions. This prevents nested branching expressions, nested multi-select-list expressions within other multi expressions, and nested or-expressions within any multi-expression.

By allowing any expression to be nested within a multi-select-list and multi-select-hash expression, we can trim down several grammar rules and provide customers with a much more flexible expression DSL.

Supporting arbitrarily nested expressions within other expressions requires:

  • Updating the grammar to remove non-branched-expr

  • Updating compliance tests to add various permutations of the grammar to ensure implementations are compliant.

  • Updating the JMESPath documentation to reflect the ability to arbitrarily nest expressions.

Nested Expression Examples

Nested branch expressions

Given:

{
    "foo": {
        "baz": [
            {
                "bar": "abc"
            }, {
                "bar": "def"
            }
        ],
        "qux": ["zero"]
    }
}

With: foo.[baz[*].bar, qux[0]]

Result:

[
    [
        "abc",
        "def"
    ],
    "zero"
]

Nested branch expressions with nested mutli-select

Given:

{
    "foo": {
        "baz": [
            {
                "bar": "a",
                "bam": "b",
                "boo": "c"
            }, {
                "bar": "d",
                "bam": "e",
                "boo": "f"
            }
        ],
        "qux": ["zero"]
    }
}

With: foo.[baz[*].[bar, boo], qux[0]]

Result:

[
    [
        [
            "a",
            "c"
        ],
        [
            "d",
            "f"
        ]
    ],
    "zero"
]

Nested or expressions

Given:

{
    "foo": {
        "baz": [
            {
                "bar": "a",
                "bam": "b",
                "boo": "c"
            }, {
                "bar": "d",
                "bam": "e",
                "boo": "f"
            }
        ],
        "qux": ["zero"]
    }
}

With: foo.[baz[*].not_there || baz[*].bar, qux[0]]

Result:

[
    [
        "a",
        "d"
    ],
    "zero"
]

No breaking changes

Because there are no breaking changes from this modification, existing multi-select expressions will still work unchanged:

Given:

{
    "foo": {
        "baz": {
            "abc": 123,
            "bar": 456
        }
    }
}

With: foo.[baz, baz.bar]

Result:

[
    {
        "abc": 123,
        "bar": 456
    },
    456
]

Modified Grammar

The following modified JMESPath grammar supports arbitrarily nested expressions and is specified using ABNF, as described in RFC4234

expression        = sub-expression / index-expression / or-expression / identifier / "*"
expression        =/ multi-select-list / multi-select-hash
sub-expression    = expression "." expression
or-expression     = expression "||" expression
index-expression  = expression bracket-specifier / bracket-specifier
multi-select-list = "[" ( expression *( "," expression ) ) "]"
multi-select-hash = "{" ( keyval-expr *( "," keyval-expr ) ) "}"
keyval-expr       = identifier ":" expression
bracket-specifier = "[" (number / "*") "]"
number            = [-]1*digit
digit             = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / "0"
identifier        = 1*char
identifier        =/ quote 1*(unescaped-char / escaped-quote) quote
escaped-quote     = escape quote
unescaped-char    = %x30-10FFFF
escape            = %x5C   ; Back slash: \
quote             = %x22   ; Double quote: '"'
char              = %x30-39 / ; 0-9
                    %x41-5A / ; A-Z
                    %x5F /    ; _
                    %x61-7A / ; a-z
                    %x7F-10FFFF