Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Parsing JSON with JMESPath in Python takes two steps: decode the JSON text into ordinary Python dictionaries, lists, and scalar values with json.loads(), then evaluate a JMESPath expression against that data with jmespath.py. For example, jmespath.search('people[0].name', data) returns the first person’s name without a chain of manual dictionary and list lookups.

The basic workflow

JMESPath is a declarative query language for JSON-shaped data. The Python implementation, jmespath.py, evaluates an expression against data that has already been decoded. It does not replace JSON decoding: keep json.loads() for text and use JMESPath for selecting or transforming the resulting structure.

import json
import jmespath

json_text = '{"people": [{"name": "Mina", "active": true}]}'
data = json.loads(json_text)

name = jmespath.search('people[0].name', data)
print(name)  # Mina

Install the package named jmespath in your project environment using your normal Python package workflow, then import it as jmespath. Keep the package and Python versions managed by your environment; the expression syntax described here comes from the JMESPath language specification.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Decode JSON before querying it

JSON text

json.loads() accepts a Python string, bytes, or bytearray containing valid JSON and returns Python values: objects become dictionaries, arrays become lists, strings remain strings, numbers become numeric values, booleans become True or False, and JSON null becomes None.

import json

payload = json.loads('{"account": {"id": 42, "enabled": true}, "tags": ["api", "python"]}')
print(type(payload))       # dict
print(payload['account']['id'])  # 42

Files and responses

For a file, read its contents and pass the string to json.loads(), or use json.load(file_object). For an HTTP client that already exposes a JSON-decoding method, use that method and pass the resulting Python object to JMESPath. Do not pass the raw response object or an undecoded JSON string to jmespath.search().

import json
from pathlib import Path

text = Path('payload.json').read_text(encoding='utf-8')
data = json.loads(text)
result = jmespath.search('account.id', data)

If decoding fails, fix the JSON input first. A malformed document is a json.JSONDecodeError problem, not a JMESPath expression problem.

Core JMESPath expressions

Goal Expression Result
Read a top-level key name The value of name
Read a nested key person.name The nested name
Read an array item people[0].name The first item’s name; indexes are zero-based
Project one key from every item people[*].name A list of names
Filter an array people[?active == `true`] Items whose active value is true
Build a named result {first: people[0].name, count: length(people)} A new object with two fields

Use backticks around JSON literals such as `true`, `10`, and `null` in expressions. A quoted string literal is written with single or double quotes according to the expression grammar; when embedding an expression inside a Python string, choose outer quotes that keep the expression readable or escape the inner ones.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Project and filter collections

Projections

A projection applies the rest of an expression to each item in an array. Given this data:

data = {
    'people': [
        {'name': 'Mina', 'role': 'admin'},
        {'name': 'Jules', 'role': 'editor'},
    ]
}

print(jmespath.search('people[*].name', data))
# ['Mina', 'Jules']

Projection behavior matters when a field is absent. A projected value that resolves to null can be omitted from the projected list, so test expressions against representative input rather than assuming the output has one position for every input object.

Filters

Filters select array elements whose condition is true. The expression below keeps only administrators:

admins = jmespath.search("people[?role == 'admin']", data)
print(admins)
# [{'name': 'Mina', 'role': 'admin'}]

Filters can compare fields, numbers, strings, and function results. Make sure the data type matches the comparison. Comparing a number to a quoted string is not the same as comparing two numbers.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Nested projections

Combine projections and nested access for API responses that contain several levels:

data = {
    'teams': [
        {'name': 'Core', 'members': [{'name': 'Mina'}, {'name': 'Jules'}]},
        {'name': 'Data', 'members': [{'name': 'Ravi'}]},
    ]
}

team_names = jmespath.search('teams[*].name', data)
member_names = jmespath.search('teams[*].members[*].name', data)
print(team_names)    # ['Core', 'Data']
print(member_names)  # [['Mina', 'Jules'], ['Ravi']]

Shape the result with multi-selects and pipes

Multi-select hashes

A multi-select hash creates an object whose keys are the labels you provide. This is useful when downstream code needs a stable, smaller schema:

expression = '{id: account.id, email: account.profile.email, labels: tags}'
small = jmespath.search(expression, {
    'account': {'id': 42, 'profile': {'email': '[email protected]'}},
    'tags': ['api', 'python']
})
print(small)
# {'id': 42, 'email': '[email protected]', 'labels': ['api', 'python']}

Pipes

A pipe passes one expression’s result into another expression. For example, people[*] | [?active == `true`] | [].name first projects the people list, filters it, then extracts names. Pipes make long queries easier to reason about because each stage has a clear input and output shape.

Functions and type-aware expressions

JMESPath includes built-in functions for operations such as measuring a collection, sorting values, converting types, and inspecting a value. Function signatures are typed: the function name, argument count, and argument types must match what the language defines.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
data = {'values': ['10', '20', '30']}

count = jmespath.search('length(values)', data)
number = jmespath.search('to_number(values[0])', data)
value_type = jmespath.search('type(values)', data)
print(count)       # 3
print(number)      # 10
print(value_type)  # array

to_number is an explicit conversion, not a substitute for validating incoming data. A non-numeric string may produce an invalid-value evaluation error, depending on the implementation’s error handling. Use type(@) or a narrower expression while investigating an unexpected payload.

Missing keys, nulls, and evaluation errors

An unknown identifier evaluates to null according to the specification. In Python, that normally appears as None:

data = {'user': {'name': 'Mina'}}

print(jmespath.search('user.email', data))  # None
print(jmespath.search('user.email || `unknown`', data))  # unknown

Treat a null result as a possible missing field, not automatically as a failure. If null is a valid business value, distinguish it from an absent key in your application logic.

Evaluation errors are different. The specification defines error classes including invalid-type, invalid-value, unknown-function, and invalid-arity. A function receiving an array when it requires a number, an unknown function name, or the wrong number of arguments can raise an exception in Python. Catch exceptions at a boundary where you can log the expression and input shape, then fix the query or normalize the data.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
try:
    result = jmespath.search('length(account.id)', data)
except Exception as exc:
    raise ValueError('JMESPath evaluation failed') from exc

Keep the original exception available during debugging; broad catching is appropriate only when your application converts query failures into a controlled response.

Reusable and efficient queries

Compile an expression once

For a query used repeatedly, compile it and reuse the parsed expression:

expression = jmespath.compile('orders[?status == `open`].id')

for payload in payloads:
    open_order_ids = expression.search(payload)
    process(open_order_ids)

This keeps the expression in one place and avoids reparsing it in your own loop. It does not change the semantics of the query.

Keep expressions understandable

  • Start with a field lookup, then add one projection or filter at a time.
  • Use a multi-select hash when the consumer needs named fields rather than a large source object.
  • Prefer explicit conversions and type checks at data boundaries.
  • Log the expression and a redacted sample of the input when diagnosing production failures.

No comparative benchmark is established here, so do not assume JMESPath is faster than hand-written Python traversal. Its principal benefit is a portable, declarative expression for extraction and transformation; ordinary Python remains clearer for application-specific branching, side effects, and complex validation.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A practical debugging checklist

  1. Confirm decoding: print or inspect type(data) and verify that the root is a dictionary or list, not a JSON string.
  2. Check the path: evaluate each segment separately, such as people, then people[0], then people[0].name.
  3. Check array indexes: indexes start at zero, and an out-of-range lookup produces a null-like result.
  4. Check the shape before projecting: items[*].name requires items to be an array.
  5. Check literal types: use backtick JSON literals for booleans, numbers, and null; do not compare a numeric field with a quoted numeric string unless that is intentional.
  6. Check function arguments: verify both arity and input type when an expression raises an evaluation error.
  7. Reduce the query: remove filters, pipes, and functions until the smallest working expression is found, then add stages back one at a time.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

JMESPath versus ordinary Python traversal

Use JMESPath when the task is primarily declarative selection: picking nested fields, filtering arrays, projecting values, or producing a smaller object with a repeatable expression. Use normal Python when the operation needs side effects, stateful logic, custom error recovery, or rules that are easier to read as statements.

You can combine both approaches. Decode and validate a payload in Python, use JMESPath to select the relevant records, then apply domain-specific Python logic to those records. This keeps the query focused and leaves business rules in regular, testable code.

Or skip the browser setup

If your workflow also needs a clean visual capture of the webpage that produced or documents an API response, ScreenshotNeo can return a screenshot or PDF from one request. It is separate from JSON parsing: use JMESPath for data and ScreenshotNeo for page captures.

ScreenshotNeo removes cookie-consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools to Claude, Cursor, and other MCP clients.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

See the ScreenshotNeo API documentation for all options. A direct call looks like this:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same request in Python:

import requests

r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

And in Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

The Free plan includes 1,000 screenshots each month with no card required; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try it.

Frequently Asked Questions

Can JMESPath modify my original Python dictionary?

No. A search evaluates an expression and returns a result; it does not provide in-place updates to the source object. Assign the returned value or perform mutations separately in Python.

What does a JMESPath search return when the input root is a list?

It can query a list directly. Use an index, projection, filter, or function appropriate to an array root, such as [?enabled == `true`] or [0].

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is JMESPath output always a Python dictionary?

No. The result can be a string, number, boolean, list, dictionary, or None, depending on the expression and input.

How can I make a query safe for changing API schemas?

Keep expressions small, treat missing fields as possible null results, validate critical fields in Python, and add tests for absent keys, empty arrays, and incorrect types.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.