Overview
When working with Ansible, you often deal with structured data made up of nested dictionaries and lists. This could be anything from device configurations to network status or inventory data. Accessing the specific piece of data you need can get messy, especially when the structure is deep or repetitive.
That’s where json_query
comes in. It’s a filter in Ansible that lets you work with structured data more efficiently. Instead of writing long chains of lookups or looping through items manually, you can use json_query
to directly extract, filter, or reshape the data you need. It helps make your playbooks cleaner and easier to read, especially when dealing with complex or deeply nested structures. It works by applying a JMESPath expression to your data, which defines exactly what you want to retrieve.
What is JMESPath?
JMESPath is a query language for JSON. Instead of writing loops and conditionals to navigate data structures, you write expressions that describe what you want to extract.
With JMESPath expressions, you can:
- Extract specific values from nested structures
- Filter arrays based on conditions
- Transform data as you extract it
This is particularly useful in network automation where you might need to extract interface names, filter active interfaces, or pull configuration details from complex data structures. Instead of writing multiple tasks with loops, json_query
lets you get what you need in a single expression.
Using json_query
can make your Ansible playbooks cleaner and easier to read. It reduces the complexity of data manipulation tasks and makes your code more straightforward.
In this post, we'll explore json_query
through examples, starting with simple queries and building up to more complex data extraction techniques.
Prerequisites and Setup
This post assumes you're somewhat familiar with Ansible, maybe you've written a few playbooks or at least understand how tasks and variables work. But even if you're just starting, don’t worry. The examples are simple, so you’ll still be able to follow along.
If you're running this on your system, make sure JMESPath is installed. Ansible relies on it to process json_query
filters. If it’s missing, you’ll see an error when the playbook runs. You can install it with:
pip install jmespath
Basic Syntax
The json_query
filter follows this pattern.
{{ data | json_query('expression') }}
The part before the pipe (|
) is your data, and the part inside the quotes is your query.
In this case, data
is just the name of your variable, it could be a list, a dictionary, or a nested structure containing both. The 'expression'
is the JMESPath syntax that tells Ansible what you want to extract from that data. You use it to navigate through keys, filter values, or reshape the structure. Together, this filter lets you pull out exactly the part of the data you care about, using a clean and readable query.
Let's start with a simple example. Say you have this user data:
user:
name: "alice"
email: "alice@company.com"
department: "engineering"
active: true
To extract just the email address, you'd use:
- name: Get user email
debug:
msg: "{{ user | json_query('email') }}"
This returns alice@company.com
That's the basic structure, you pipe your data through json_query
and provide a JMESPath expression in quotes. The expression email
simply asks for the value of the email key.
If you want to try this out, create a new YAML file and add this content.
---
- name: Alice
hosts: localhost
gather_facts: no
vars:
user:
name: "alice"
email: "alice@company.com"
department: "engineering"
active: true
tasks:
- name: Get user email
debug:
msg: "{{ user | json_query('email') }}"
I created a file called alice.yml
and then ran ansible-playbook alice.yml
. You should see output showing the email address.

Selecting JSON data with JSON queries
Now let's look at a few more examples to see how json_query
handles different scenarios. I'd recommend following along and trying these out yourself. Create new playbook files or modify the existing ones as we go through each example. The best way to get comfortable with JMESPath expressions is to run them and see the results.
Select a Single Element
Let’s break down the first example playbook that uses json_query
to extract a value from a nested dictionary.
---
- name: Single Element Example
hosts: localhost
gather_facts: no
vars:
interface: {
"name": "eth0",
"ipv4": {
"address": "192.168.1.10",
"masklen": 24
},
"status": "up"
}
tasks:
- name: Extract IP address using json_query
debug:
msg: "{{ interface | json_query('ipv4.address') }}"
Output
ok: [localhost] => {
"msg": "192.168.1.10"
}
In this playbook, we have a variable called interface
that holds a dictionary with keys like name
, ipv4
, and status
. Inside ipv4
, there’s another dictionary that holds the IP address and subnet mask.
The task uses the debug
module along with the json_query
filter. The expression 'ipv4.address'
tells Ansible to look inside the interface
dictionary, then go into the ipv4
key, and finally extract the address
value. This works just like navigating keys in a dictionary, but with a cleaner syntax. So instead of writing nested lookups or custom logic, this one line gives you exactly what you want- the IP address.
This makes it easier to work with structured data in a readable way, especially when the data is nested or you want to pick just a specific field.
Please note that the vars
section in an Ansible playbook is where you define static data that can be used throughout your tasks. It’s a simple way to declare values like strings, dictionaries, or lists that your playbook will reference.
The debug
module is used to print messages or variable values during playbook execution. It's helpful for testing and understanding what your data looks like at a specific point in the playbook. The msg
parameter in the debug
module is used to define what message should be printed.
JMESPath Validator
If you're just getting started or want to quickly test your queries, you can use Packet Coder's JMESPath Validator tool. It's a simple web-based tool that lets you paste your JSON data, write a JMESPath query, and see the result instantly. You can also share the link with someone else, which makes it easy to collaborate or ask questions.
To use it, paste your JSON-like data into the first box labelled JSON String. Then, in the second box labelled Query, write your JMESPath expression.

The output will show up right below, so you can confirm it’s doing what you expect. This is a great way to experiment without running a full playbook.
Below is a shareable link for this specific example. Feel free to check it out and experiment with the query or tweak the data to see how it behaves.
Try It Out Live ➜Selecting Multiple Values from a List
Building on the first example, we now move from a single interface to a list of interfaces. Instead of just one dictionary, the interfaces
variable now holds a list of dictionaries, each representing one interface with its name and IP details.
---
- name: List of Interfaces
hosts: localhost
gather_facts: no
vars:
interfaces: [
{
"name": "eth0",
"ipv4": {
"address": "192.168.1.10",
"masklen": 24
}
},
{
"name": "eth1",
"ipv4": {
"address": "10.0.0.5",
"masklen": 24
}
}
]
tasks:
- name: Extract all IP addresses
debug:
msg: "{{ interfaces | json_query('[].ipv4.address') }}"
Output
ok: [localhost] => {
"msg": [
"192.168.1.10",
"10.0.0.5"
]
}
The structure of each interface remains the same, but by wrapping them in square brackets and separating them with commas, we turn them into a list.
In the task, the json_query
expression also changes slightly. We now use [].ipv4.address
. The empty square brackets ([]
) tell JMESPath to iterate through each item in the list and extract the value at ipv4.address
. This gives us a list of IP addresses from all interfaces.
Similarly, if you want to extract all the interface names, you would use:
- name: Extract all interface names
debug:
msg: "{{ interfaces | json_query('[].name') }}"
Output
ok: [localhost] => {
"msg": [
"eth0",
"eth1"
]
}
Try It Out Live ➜
Filtering a List with Conditions
In this example, we’re using a filter expression to extract the IP address of eth1
from the list of interfaces.
---
- name: Extract IP of a Specific Interface
hosts: localhost
gather_facts: no
vars:
interfaces: [
{
"name": "eth0",
"ipv4": {
"address": "192.168.1.10",
"masklen": 24
}
},
{
"name": "eth1",
"ipv4": {
"address": "10.0.0.5",
"masklen": 24
}
}
]
tasks:
- name: Extract IP address of eth1
debug:
msg: "{{ interfaces | json_query(\"[?name=='eth1'].ipv4.address | [0]\") }}"
The key part of the query is [?name=='eth1'].ipv4.address | [0]
The ?
is a filter operator in JMESPath. It’s used to go through each item in the list and only keep the ones that match the condition. In our case, name=='eth1'
filters the list to just the interface with that name.
The ==
is a comparison operator, just like in Python or most other languages. It checks if the name
field in each item matches the string 'eth1'
.
Because we’re using double quotes on the outside of the expression, we need to escape the inner quotes around eth1
with backslashes (\"
). This is required in YAML so the string is parsed correctly.
Once the interface is filtered, .ipv4.address
gets the IP address of the matching item. But JMESPath still returns that result as a list. That’s why we add [0]
at the end to extract just the first item from the list and get a plain string like "10.0.0.5"
instead of a list like ["10.0.0.5"]
.
Extracting a List of Dictionaries with Selected Fields
Let’s step away from network data for a moment and look at a more general use case. In this example, we’ll look at how to return a simplified version of the data - just the name and email for each user. This is useful when you want to clean up or reshape your data before using it.
---
- name: Extract name and email from user list
hosts: localhost
gather_facts: no
vars:
users: [
{
"name": "alice",
"email": "alice@example.com",
"role": "admin"
},
{
"name": "bob",
"email": "bob@example.com",
"role": "user"
}
]
tasks:
- name: Get only name and email
debug:
msg: "{{ users | json_query('[].{name: name, email: email}') }}"
This uses a JMESPath expression with curly braces to build a new dictionary for each item in the list. The result will be:
[
{
"email": "alice@example.com",
"name": "alice"
},
{
"email": "bob@example.com",
"name": "bob"
}
]
As always, feel free to try this out in Packet Coder's JMESPath validator. It’s a quick way to test your query and see the results in real time. Click below to access this specific query and data.
Try It Out Live ➜
Closing Up
To wrap up, json_query
is one of those tools in Ansible that can make your playbooks cleaner, especially when dealing with structured data. Instead of looping through lists or writing extra tasks to filter values, you can use one line to get exactly what you need. Once you understand the basics of JMESPath, it becomes second nature. Hope this post gave you a good starting point to explore it further.