JMESPath is a query language for JSON which you can use to extract and transform elements in a JSON document. So what has that got to do with Azure CLI?

Azure CLI command results are either a JSON array or dictionary. Even when an output format other than JSON is specified, CLI command results are first treated as JSON to enable queries to be performed, before outputted into the format specified.

All CLI commands supports the --query parameter, and accepts a JMESPath argument.

If we send the following command to azure

az vm show -g demo -n test-vm -o json

we will get a similar result as

{
  "additionalCapabilities": null,
  "availabilitySet": null,
  "diagnosticsProfile": {
    "bootDiagnostics": {
      "enabled": true,
      "storageUri": "https://xxxxxx.blob.core.windows.net/"
    }
  },
  ...
  "osProfile": {
    "adminPassword": null,
    "adminUsername": "azureuser",
    "allowExtensionOperations": true,
    "computerName": "test-vm",
    "customData": null,
    "linuxConfiguration": {
      "disablePasswordAuthentication": true,
      "provisionVmAgent": true,
      "ssh": {
        "publicKeys": [
          {
            "keyData": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMobZNJTqgjWn/IB5xlilvE4Y+BMYpqkDnGRUcA0g9BYPgrGSQquCES37v2e3JmpfDPHFsaR+CPKlVr2GoVJMMHeRcMJhj50ZWq0hAnkJBhlZVWy8S7dwdGAqPyPmWM2iJDCVMVrLITAJCno47O4Ees7RCH6ku7kU86b1NOanvrNwqTHr14wtnLhgZ0gQ5GV1oLWvMEVg1YFMIgPRkTsSQKWCG5lLqQ45aU/4NMJoUxGyJTL9i8YxMavaB1Z2npfTQDQo9+womZ7SXzHaIWC858gWNl9e5UFyHDnTEDc14hKkf1CqnGJVcCJkmSfmrrHk/CkmF0ZT3whTHO1DhJTtV stramer@contoso",
            "path": "/home/azureuser/.ssh/authorized_keys"
          }
        ]
      }
    },
    "secrets": [],
    "windowsConfiguration": null
  },
  ....
}

This is really not a pretty sight. If we are interested in some specific values, then we have to use the JMESPath query to look for those values.

Basic Expressions

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

{"a": "foo", "b": "bar", "c": "baz","d"; {"e" :value}}

Let’s see what we get by using the queries below

a => "foo"
c => "baz"
q => null

When a key does not exists, the query will return null, (or the language equivalent of null) .

You can use a subexpression to return to nested values in a JSON object:

d.e => value
d.f => null

With our Azure CLI command earlier, if we want to check the status of the
bootDiagnostics , we can use

az vm show -g demo -n test-vm -query diagnosticsProfile.bootDiagnostics.enabled

which will return

true

Index Expressions allow us to select a specific element in a list. This is similar to array access in common programming languages. Indexing is 0 based.

Example

["a", "b", {"c": [
      {"d": [0, [1, 2]]},
      {"d": [3, 4]}
    ]}
]
[0] => "a"
[1] => "b"
[2]c[0].d[1] => [1, 2]

If you specify an index that’s larger than the list, a value of null is returned. 

Slicing

Slices allow you to select a contiguous subset of an array. If has the format [start:end:step]. By default, the start value is 0, the step value is 1, and all these values are optional.
The ending index is the first index which you do not want included in the slice.

Example

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0:4] => [0,1,2,3]
[:4} => [0,1,2,3]
[2:4:2] => [2]
[::2] => [0,2,4,6,8]
[::] => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Projections

Consider the following JSON document;

{
  "people": [
    {"name": "James", "age": 30,"state": {"name": "up"}},
    {"name": "Jacob", "age": 40,"state": {"name": "down"}},
    {"name": "Jayden", "age": 50,"state": {"name": "up"}},
    {"missing": "different"}
  ],
  "foo": {"bar": "baz"}
}

Say we want to select all people who have a name, or put differently, want to select the names of all people, how do we do it? That is where projections come in.

Projections allow you to apply an expression to a collection of elements. Example is

people[*].name

Projections break an expression into two parts, left hand side (LHS) and right hand side (RHS).

  • The LHS is the section of the expression before the . character.
  • The LHS creates a JSON array of initial values.
  • The RHS is the expression to project for each element in the JSON array created by LHS.

There are four kinds of projections:

  • List Projections
  • Slice Projections
  • Object Projections
  • Flatten Projections

List and Slice Projections

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

What does this expression mean?

people[*].name
  • The LHS is people[*] and RHS is name
  • A wildcard * is used to index the people array

The LHS produces a list projection

[
  {
    "name": "James",
    "age": 30,
    "state": {"name": "up"}
  },
  {
    "name": "Jacob",
    "age": 40,
    "state": {"name": "down"}
  },
  {
    "name": "Jayden",
    "age": 50,
    "state": {"name": "up"}
  },
  {
    "missing": "different"
  }
]

The RHS .name expression is then projected over the array, which returns

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

The results are collected into a JSON array and returned as the result of the expression. The last element, {“missing”: “different”} evaluates to null, so it is not added to the returned array.

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

people[:2].name

returns

[
  "James",
  "Jacob"
]

Object Projections

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 right hand side of the projection onto the list of values.

Consider

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

For the expression ops.*.numArgs, the LHS (ops.*) will produce a list projection

[
  {
    "numArgs": 2
  },
  {
    "numArgs": 3
  },
  {
    "variadic": true
  }
]

And with the RHS expression .numArgs, we get

[
  2,
  3
]

null values are not included in the final result.

Other posts in this series