Updated on 2023-02-27 GMT+08:00

How Do I Use JMESPath Expressions?

You can use JMESPath expressions as follows:

  • Basic expression
    • Identifier

      The simplest JMESPath expression is an identifier, which specifies a key in a JSON object:

      {"a": "foo", "b": "bar", "c": "baz"}

      For the preceding JSON content, if the expression is a, the result foo is obtained.

      If you specify a key that does not exist, KooCLI displays an error message and outputs the original JSON result.

    • Subexpression

      Use a subexpression to return the nested value in a JSON object.

      {"a": {"b": {"c": {"d": "value"}}}}

      For the preceding JSON content, if the expression is a.b.c.d, the result value is obtained.

      If you specify a key that does not exist, KooCLI displays an error message and outputs the original JSON result.

    • Index expression

      Index expressions allow you to select a specific element in a list. Indexing is zero-based.

      ["a", "b", "c", "d", "e", "f"]

      For the preceding JSON content, if the expression is [1], the result b is obtained.

      If you specify an index that is larger than the list, KooCLI displays an error message and outputs the original JSON result. You can also use negative indexing to index from the end of the list. [-1] indicates the last element in the list, and [-2] indicates the last but one element.

    • You can combine identifiers, subexpressions, and index expressions to access JSON elements.
      {"a": {
        "b": {
          "c": [
            {"d": [0, [1, 2]]},
            {"d": [3, 4]}
          ]
        }
      }}

      For the preceding JSON content, if the expression is a.b.c[0].d[1][0], the result 1 is obtained.

  • Slice
    The general form of a slice is [start:stop:step]. By default, the step value is 1. Therefore, the form can be [start:stop]. Slices allow you to select a contiguous subset of an array. In its simplest form, you can specify the starting index and the ending index. The ending index will not be included in the slice.
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    For the preceding JSON content, if the expression is [0:5], the following result is obtained:

    [
      0,
      1,
      2,
      3,
      4
    ]

    This slice result contains the elements 0, 1, 2, 3, and 4. The element at index 5 is not included.

    If the expression is [5:10], the following result is obtained:

    [
      5,
      6,
      7,
      8,
      9
    ]

    The two examples above can be shortened. If the start or stop value is omitted, the array starts from the first element or stops from the last element by default. For example:

    If the expression is [:5], the following result is obtained:

    [
      0,
      1,
      2,
      3,
      4
    ]
    By default, the step value is 1, which means to include every element in the range specified by the start and stop value. You can use the step value to skip over elements. For example, to select only the even elements from an array, use the expression [::2]. The following result is obtained:
    [
      0,
      2,
      4,
      6,
      8
    ]

    Also note that in this example, the start and stop values are omitted, meaning that 0 is used for the start value and 10 is used for the stop value. In this example, the expression [::2] is equivalent to [0:10:2].

    If the step value is negative, the slice is created in reverse order. For example, if the expression is [::-1], the following result is obtained:

    [
      9,
      8,
      7,
      6,
      5,
      4,
      3,
      2,
      1,
      0
    ]
  • Projection

    Projections are one of the key features of JMESPath. It allows you to apply an expression to a collection of elements. There are five types of projection: list, slice, object, flatten, and filter projections.

    • List projection

      A wildcard expression creates a list projection, which is a projection over a JSON array.

      {
        "people": [
          {"first": "James", "last": "d"},
          {"first": "Jacob", "last": "e"},
          {"first": "Jayden", "last": "f"},
          {"missing": "different"}
        ],
        "foo": {"bar": "baz"}
      }

      For the preceding JSON content, if the expression is people[*].first, the following result is obtained:

      [
        "James",
        "Jacob",
        "Jayden"
      ]

      In the above example, the first expression is applied to each element in the people array. The results are collected into a JSON array and returned as the result of the expression. For example, the expression foo[*].bar.baz[0] projects the bar.baz[0] expression to each element in the foo array.

      Pay attention to the following when using projections:

      • Projections are evaluated as two steps. The left hand side (LHS) creates a JSON array of initial values. The right hand side (RHS) of a projection is the expression to project for each element in the JSON array created by the LHS. Each projection type has slightly different semantics when evaluating the LHS and RHS.
      • If the result of the expression projected onto an individual array element is null, then that value is omitted from the collected set of results.
      • You can stop the projection using a pipe expression (discussed later).
      • A list projection is only valid for a JSON array. If the LHS cannot create a JSON array of initial values, KooCLI displays an error message and outputs the original JSON result.

      Note that people[*].first only included three elements, even though the people array has four elements. This is because the last element {"missing": "different"} evaluates to null when the expression first is applied, and null values are not added to the collected result array. If you try the expression foo[*].bar, KooCLI displays an error message and outputs the original JSON result, because the value associated with the foo key is a JSON object, not an array.

    • Slice projection

      Slice projections are almost identical to list projections, with the exception that the LHS is the result of evaluating the slice, which may not include all the elements in the original list:

      {
        "people": [
          {"first": "James", "last": "d"},
          {"first": "Jacob", "last": "e"},
          {"first": "Jayden", "last": "f"},
          {"missing": "different"}
        ],
        "foo": {"bar": "baz"}
      }

      For the preceding JSON content, if the expression is people[:2].first, the following result is obtained:

      [
        "James",
        "Jacob"
      ]
    • Object projection

      Whereas a list projection is defined for a JSON array, an object projection is defined for a JSON object. You can create an object projection using the * syntax. This will create a list of the values of the JSON object, and project the RHS of the projection onto the list of values.

      {
        "ops": {
          "functionA": {"numArgs": 2},
          "functionB": {"numArgs": 3},
          "functionC": {"variadic": true}
        }
      }

      For the preceding JSON content, if the expression is ops.*.numArgs, the following result is obtained:

      [
        2,
        3
      ]

      The object projection can be divided into two parts. The LHS is ops and the RHS is numArgs. In the preceding example, * creates a JSON array of the values associated with the ops JSON object. The RHS of the projection, numArgs, is then applied to the JSON array, resulting in the final array of [2, 3].

      The following describes how to project an object:

      1. The LHS is evaluated to create the initial array to be projected:

        evaluate(ops, inputData) -> [{"numArgs": 2}, {"numArgs": 3},{"variadic": True}]

      2. Then the RHS is applied to each element in the array:

        evaluate(numArgs, {numArgs: 2}) -> 2

        evaluate(numArgs, {numArgs: 3}) -> 3

        evaluate(numArgs, {variadic: true}) -> null

      3. Any null values are not included in the final result, so the result of the entire expression is [2, 3].
    • Flatten projection

      More than one projection can be used in a JMESPath expression. In the case of a list/object projection, the original data structure is preserved when a projection is created within another projection.

      {
        "reservations": [
          {
            "instances": [
              {"state": "running"},
              {"state": "stopped"}
            ]
          },
          {
            "instances": [
              {"state": "terminated"},
              {"state": "running"}
            ]
          }
        ]
      }

      In the preceding JSON content, the expression reservations[*].instances[*].state indicates that the value of the top-level key reservations is an array. For each element in the reservations array, project the instances[*].state expression. Within each element in the reservations array, there is an instances key which itself is a value, and a subprojection is created for each element in the instances array. The following result is obtained:

      [
        [
          "running",
          "stopped"
        ],
        [
          "terminated",
          "running"
        ]
      ]

      The result is a nested list. The outer list is the projection of reservations[*], and the inner list is the projection of state created from instances[*].

      What if you do not care which reservations the instances belongs to and you want a list of all the state values? That is, your expected result is as follows:

      [
        "running",
        "stopped",
        "terminated",
        "running"
      ]

      This is what a flatten projection solves. To get the expected result, you can use [] instead of [*] to flatten a list, that is, use reservations[].instances[].state.

      Rules of thumb to use for the flatten operator [] are as follows:

      • It flattens sublists into the parent list (not recursively, just one level).
      • It creates a projection, so anything on the RHS of the flatten projection is projected onto the newly created flattened list.

      You can also use [] on its own to flatten a list.

      [
        [0, 1],
        2,
        [3],
        4,
        [5, [6, 7]]
      ]

      For the preceding JSON content, if the expression is [], the following result is obtained:

      [
        0,
        1,
        2,
        3,
        4,
        5,
        [
          6,
          7
        ]
      ]

      If you use [][] to flatten the result of the expression again, the result of [0, 1, 2, 3, 4, 5, 6, 7] is obtained.

    • Filter projection

      Filter projections allow you to filter the LHS of the projection before evaluating the RHS of a projection.

      {
        "machines": [
          {"name": "a", "state": "running"},
          {"name": "b", "state": "stopped"},
          {"name": "b", "state": "running"}
        ]
      }

      For the preceding JSON content, if the expression is machines[?state=='running'].name, the following result is obtained:

      [
        "a",
        "b"
      ]

      A filter expression is defined for an array and has the general form LHS[?<Expression><Comparator><Expression>]RHS. The following comparators are supported: ==, !=, <, <=, >, >=.

  • Pipe expression

    Projection is an important concept in JMESPath. However, sometimes projection semantics are not what you want. A common scenario is when you want to operate the result of a projection rather than projecting an expression onto each element in the array. For example:

    {
      "people": [
        {"first": "James", "last": "d"},
        {"first": "Jacob", "last": "e"},
        {"first": "Jayden", "last": "f"},
        {"missing": "different"}
      ],
      "foo": {"bar": "baz"}
    }

    The expression people[*].first will give you an array containing the first names of everyone in the people array. What if you wanted the first element in that array? If you tried people[*].first[0] that you evaluate first[0] for each element in the people array, and because indexing is not defined for strings, the final result would be an empty array, []. To obtain the desired result, you can use a pipe expression, <Expression> | <expression>. For the preceding JSON content, if the expression is people[*].first | [0], the result is James.

    In the pipe expression above, the RHS of the list projection is first. When a pipe is encountered, the result up to that point is passed to the RHS of the pipe expression. The pipe expression is evaluated as:

    1. evaluate(people[*].first, inputData) -> ["James", "Jacob", "Jayden"]
    2. evaluate([0], ["James", "Jacob", "Jayden"]) -> "James"
  • MultiSelect

    Multiselect expressions are classified into multiselect lists and multiselect hashes. Multiselect expressions allow you to create elements that do not exist in the JSON data. A multiselect list creates a list and a multiselect hash creates a JSON object.

    • Multiselect list
      {
        "people": [
          {
            "name": "a",
            "state": {"name": "up"}
          },
          {
            "name": "b",
            "state": {"name": "down"}
          },
          {
            "name": "c",
            "state": {"name": "up"}
          }
        ]
      }

      For the preceding JSON content, if the expression is people[].[name,state.name], the following result is obtained:

      [
        [
          "a",
          "up"
        ],
        [
          "b",
          "down"
        ],
        [
          "c",
          "up"
        ]
      ]

      In the expression above, [name,state.name] is a multiselect list. It indicates that a list of two elements is created. The first element is the result of evaluating the name expression against the list element, and the second element is the result of evaluating state.name. Each list element will therefore create a two-element list, and the final result of the entire expression is a list of two-element lists.

      Unlike a projection, the result of the expression is always included, even if the result is a null. If you change the above expression to people[].[foo,bar], each two-element list will be [null, null].

      [
        [
          null,
          null
        ],
        [
          null,
          null
        ],
        [
          null,
          null
        ]
      ]
    • Multiselect hash

      A multiselect hash has the same basic idea of a multiselect list. The only difference is that a multiselect hash creates a hash instead of an array.

      {
        "people": [
          {
            "name": "a",
            "state": {"name": "up"}
          },
          {
            "name": "b",
            "state": {"name": "down"}
          },
          {
            "name": "c",
            "state": {"name": "up"}
          }
        ]
      }

      For the preceding JSON content, if the expression is people[].{Name:name,State:state.name}, the following result is obtained:

      [
        {
          "Name": "a",
          "State": "up"
        },
        {
          "Name": "b",
          "State": "down"
        },
        {
          "Name": "c",
          "State": "up"
        }
      ]
  • Function

    JMESPath supports function expressions, for example:

    {
      "people": [
        {
          "name": "b",
          "age": 30,
          "state": {"name": "up"}
        },
        {
          "name": "a",
          "age": 50,
          "state": {"name": "down"}
        },
        {
          "name": "c",
          "age": 40,
          "state": {"name": "up"}
        }
      ]
    }

    For the preceding JSON content, if the expression is length(people), the result is 3.

    Functions can be used to transform and filter data in a powerful way. For details about the built-in functions, see Which Built-in Functions Are Supported by JMESPath?

    The following are some example functions.

    This example prints the name of the oldest person in the people array:

    {
      "people": [
        {
          "name": "b",
          "age": 30
        },
        {
          "name": "a",
          "age": 50
        },
        {
          "name": "c",
          "age": 40
        }
      ]
    }

    For the preceding JSON content, if the expression is max_by(people,&age).name, the result is a.

    Functions can also be combined with filter expressions. In the following example, the JMESPath expression finds all elements in myarray that contains the string foo.

    {
      "myarray": [
        "foo",
        "foobar",
        "barfoo",
        "bar",
        "baz",
        "barbaz",
        "barfoobaz"
      ]
    }

    For the preceding JSON content, if the expression is myarray[?contains(@,'foo')==`true`], the following result is obtained:

    [
      "foo",
      "foobar",
      "barfoo",
      "barfoobaz"
    ]

    The @ character in the above example refers to the current element being evaluated in myarray. The expression contains(@, `foo`) will return true if the current element in the myarray array contains the string foo.

    Pay attention to the following when using functions:

    • Function arguments have types. If an argument for a function has the wrong type, KooCLI displays an error message and outputs the original JSON result. There are functions that can convert arguments (to_string, to_number) to their proper type.
    • The number of function parameters is limited. If a function is called with the wrong number of arguments, KooCLI displays an error message and outputs the original JSON result.