mrkeyoor.com_
Wed 05 Aug 23:08 UTC
PyPIUtilsupdated 05 Aug 2026

jmespath

jmespath is the Python implementation of JMESPath, a query language for JSON: you write a declarative expression like 'reservations[*].instances[?state==`running`].id' and it extracts exactly that from nested dicts and lists. The language has a formal ABNF grammar, a spec, and a cross-implementation compliance suite, so expressions behave identically in Python, the AWS CLI, and other ports. The 150M weekly downloads are mostly because boto3 and the AWS CLI depend on it, so it is already installed on practically every machine that touches AWS.

Verdict

The most disciplined JSON query language in Python: spec-frozen, portable, and already installed anywhere boto3 lives. Use it for config-driven extraction and anything AWS-adjacent; skip it when you need recursive search or real transformation, which are exactly the features it refuses to have.

API stability5/5Two public functions (search, compile) unchanged for a decade, and the language itself is governed by a spec with compliance tests; 1.x has never broken an expression.
Docs4/5jmespath.org has an interactive tutorial, examples, and the full spec, which is more than most utility libraries; the Python-specific docs are just a README, and custom functions are thinly covered.
Maintenance3/5Alive but glacial: 1.1.0 in January 2026 was the first release in about four years, and issues sit long; being an AWS SDK dependency keeps it from actually dying.
Ecosystem4/5Implementations exist across many languages and it is baked into the AWS CLI and boto3; the plugin and tooling scene beyond that is small because the language is intentionally closed.

Use it if

  • You need user-supplied or config-driven JSON queries: a JMESPath string in YAML config is far safer and more portable than eval or ad-hoc key paths
  • You work with AWS: boto3 paginators, the CLI's --query flag, and CloudWatch tooling all speak JMESPath, so learning it once pays off across the stack
  • You want spec-frozen behavior: the language has a formal grammar and compliance tests, so an expression that works today works in every version and implementation
  • You are already shipping boto3, which means jmespath is in your environment for free
Skip it if

Setup reality

pip install jmespath is as easy as it gets: pure Python, zero dependencies, supports Python 3.9+. The friction is in the language, not the install: missing keys return None silently instead of raising, so typos in expressions fail quietly; numeric and string literals need backticks or single quotes with rules people always get wrong; and the custom-functions API has been labeled experimental for a decade. Also know the release rhythm: 1.1.0 in January 2026 was the first release since 1.0.1 in 2022, which is fine for a spec-frozen language but means bugs sit for years.

Patterns

Extract a nested valuebasic-search

import jmespath

data = {"foo": {"bar": "baz"}}
jmespath.search("foo.bar", data)   # 'baz'
jmespath.search("foo.missing", data)  # None, no exception

Missing paths return None silently rather than raising. Great for optional fields, terrible for catching typos in expressions; test expressions against real payloads.

Compile once, search many documentscompile-reuse

import jmespath

expr = jmespath.compile("items[*].id")
for page in pages:
    ids = expr.search(page)

compile() parses the expression once; in loops over many documents this skips reparsing, the same way re.compile does for regexes.

Project over listslist-projection

data = {"users": [{"name": "ana"}, {"name": "raj"}]}
jmespath.search("users[*].name", data)   # ['ana', 'raj']
jmespath.search("users[-1].name", data)  # 'raj'
jmespath.search("users[0:1].name", data) # ['ana'] (python-style slice)

Projections stop at None: if 'users' is missing the whole expression yields None, not an empty list. Guard with '|| `[]`' when downstream code expects a list.

Filter with a predicatefilter-expression

data = {"instances": [
    {"id": "i-1", "state": "running", "cpu": 4},
    {"id": "i-2", "state": "stopped", "cpu": 8},
]}
jmespath.search("instances[?state=='running'].id", data)  # ['i-1']
jmespath.search("instances[?cpu > `4`].id", data)         # ['i-2']

String literals use single quotes, numbers need backticks: 'cpu > 4' without backticks is a parse error people hit constantly.

Reshape with multiselect hashesmultiselect-reshape

data = {"instances": [{"id": "i-1", "state": "running", "az": "us-east-1a"}]}
jmespath.search(
    "instances[*].{name: id, zone: az}", data
)  # [{'name': 'i-1', 'zone': 'us-east-1a'}]

Multiselect ({} for dicts, [] for lists) is the only reshaping JMESPath does; anything more complicated is a sign you want glom or plain Python.

Pipe results through built-in functionspipe-and-functions

data = {"pkgs": [{"name": "a", "dl": 90}, {"name": "b", "dl": 300}]}
jmespath.search("sort_by(pkgs, &dl)[-1].name", data)  # 'b'
jmespath.search("pkgs[*].dl | max(@)", data)          # 300
jmespath.search("length(pkgs)", data)                  # 2

'&' makes an expression-reference (needed by sort_by, max_by, min_by) and '@' is the current node. The built-in function list is fixed by the spec.

Flatten nested listsflatten-nested

data = {"reservations": [
    {"instances": [{"id": "i-1"}, {"id": "i-2"}]},
    {"instances": [{"id": "i-3"}]},
]}
jmespath.search("reservations[*].instances[*].id", data)
# [['i-1', 'i-2'], ['i-3']]
jmespath.search("reservations[].instances[].id", data)
# ['i-1', 'i-2', 'i-3']

[*] preserves nesting per projection level, [] flattens one level. This exact pair (reservations/instances) is the canonical AWS EC2 example because everyone hits it there.

Filter boto3 pagination with JMESPathboto3-paginate-search

import boto3

ec2 = boto3.client("ec2")
paginator = ec2.get_paginator("describe_instances")
running = paginator.paginate().search(
    "Reservations[].Instances[?State.Name=='running'].InstanceId[]"
)
for instance_id in running:
    print(instance_id)

PageIterator.search() applies the expression across all pages lazily; this is jmespath's biggest real-world deployment and mirrors the AWS CLI --query flag exactly.

Register a custom functioncustom-functions

import jmespath
from jmespath import functions

class Custom(functions.Functions):
    @functions.signature({"types": ["string"]})
    def _func_upper(self, s):
        return s.upper()

opts = jmespath.Options(custom_functions=Custom())
jmespath.search("upper(name)", {"name": "keyoor"}, options=opts)  # 'KEYOOR'

Method names must start with _func_ and the signature decorator enforces argument types. The README has marked this API experimental for years, so pin if you rely on it.

Provide fallbacks with the or-operatordefault-fallback

data = {"config": {}}
jmespath.search("config.timeout || `30`", data)      # 30
jmespath.search("config.tags || `[]`", data)          # []

'||' returns the right side when the left is null or empty, which is the idiomatic way to avoid None leaking into code that expects a value.

Alternatives

PackageRegistryPick it when
jsonpath-ngPyPIYou need JSONPath semantics, especially recursive descent ('$..price') that JMESPath deliberately does not have.
glomPyPIYou want Pythonic path access with defaults, deep assignment, and data reshaping rather than a separate query-string language.
jqPyPIYour team already thinks in jq syntax and you want the same expressions in Python via bindings to the C library.