JMESPath per principianti: query di proiezione che battono JSONPath
In sintesi: JMESPath rimodella lotti di oggetti più direttamente di JSONPath. Questa guida per principianti copre sintassi, proiezioni, pipe, funzioni integrate e uso con AWS CLI.

In sintesi: usa JSON Pointer per localizzare per posizione, JSONPath per filtrare o ricorrere, e JMESPath quando devi rimodellare il risultato o proiettarlo sulla riga di comando; un singolo campo noto non necessita di nessuno dei tre.
Se il tuo obiettivo è rimodellare un lotto di oggetti in una forma diversa, JMESPath è spesso più diretto di JSONPath. Non cerca nodi; costruisce un risultato.
1. Cos'è JMESPath
JMESPath è un linguaggio di interrogazione progettato specificamente per JSON. Fu creato in origine per filtrare l'output di AWS CLI e in seguito si è diffuso in molti linguaggi attraverso implementazioni come jmespath.js, jmespath.py e go-jmespath. Il suo obiettivo differisce da JSONPath:
- JSONPath si concentra sul selezionare un insieme di nodi per percorso e condizione, e restituisce un insieme di nodi;
- JMESPath si concentra sul trasformare il JSON di input e restituisce un valore di qualsiasi forma.
Questa differenza rende JMESPath particolarmente adatto alle proiezioni di liste, alla ridenominazione dei campi e alla rifinitura dei risultati, come ridurre venti campi da una risposta API ai tre campi di cui il frontend ha davvero bisogno. Se non hai ancora letto le basi di JSONPath, inizia da Il linguaggio di interrogazione JSONPath in pratica.
2. Sintassi di base: dagli identificatori agli indici
JMESPath usa meno simboli di JSONPath e si legge più come un'espressione di estrazione:
| Sintassi | Significato | Esempio |
|---|---|---|
name |
Accede a un membro dell'oggetto | people |
a.b |
Accede a membri annidati | store.bicycle.color |
[0] |
Accede a un indice dell'array | people[0] |
[-1] |
Accede all'ultimo elemento | people[-1] |
[0:2] |
Slice, inizio incluso e fine esclusa | people[0:2] |
[*] |
Proiezione di lista | people[*].name |
[] |
Proiezione di appiattimento | people[].tags[] |
[?condition] |
Proiezione di filtro | people[?age > 18] |
{key: expr} |
Hash multi-selezione | {name: name, age: age} |
[expr, expr] |
Lista multi-selezione | [name, age] |
| |
Passa il risultato sinistro al lato destro | people[*].age | [0] |
Un esempio minimo:
{
"people": [
{ "name": "Alice", "age": 31, "city": "Shanghai" },
{ "name": "Bob", "age": 17, "city": "Beijing" }
]
}
people[0].name → "Alice"
people[-1].city → "Beijing"
people[0:2].name → ["Alice", "Bob"]
Nota perché people[0:2].name restituisce un array: uno slice restituisce una lista, e il successivo .name viene automaticamente proiettato su ogni elemento di quella lista. Questa è la più grande differenza tra JMESPath e l'accesso ordinario agli oggetti.
3. Proiezioni: trasformare una lista in un'altra
Una proiezione è il concetto centrale di JMESPath. Funziona in due passi: valuta ogni elemento di una lista, poi raccoglie i risultati in una nuova lista. Gli elementi che valutano a null vengono eliminati automaticamente.
people[*].name // ["Alice", "Bob"]
people[*].{name: name, age: age} // a list of objects
people[?age > 18].name // ["Alice"]
La seconda riga è un'espressione di proiezione tipica: people[*].{name: name, age: age}. Ricostruisce ogni elemento come { "name": ..., "age": ... } e scarta il campo city in più. Rispetto a JSONPath, che ti dà nodi che poi mappi nel codice, questo risparmia un ciclo.
Vale la pena distinguere due tipi di proiezioni:
[*]preserva la struttura della lista originale e salta i valori null;[]è una proiezione di appiattimento che prima collassa un livello di liste annidate, il che si adatta a espressioni comepeople[].tags[]che necessitano di "tutti i tag di tutte le persone".
4. Pipe e multi-selezione
La pipe | significa "ferma la proiezione corrente e tratta il risultato come un nuovo input". Il suo uso più comune è prendere un indice dopo una proiezione:
people[*].name | [0] → "Alice"
people[*].age | sort(@) | [-1] → the maximum age
Senza la pipe, people[*].name[0] viene interpretato come prendere il primo carattere di ogni nome, che non è ciò che vuoi. La pipe è il simbolo più facilmente trascurato e più utile di JMESPath.
Esistono due forme di multi-selezione:
// Multi-select hash: the result is an object
people[0].{name: name, age: age}
// Multi-select list: the result is an array
people[0].[name, age]
Gli hash multi-selezione sono adatti a costruire risultati con nome, mentre le liste multi-selezione sono adatte al consumo posizionale. Entrambi possono essere annidati dentro le proiezioni, ma nota: in un hash multi-selezione, una chiave il cui valore è null viene comunque mantenuta, mentre una proiezione elimina l'intero elemento null.
5. Funzioni integrate
Lo standard JMESPath definisce un insieme di funzioni. Quella comuni includono:
| Funzione | Scopo | Esempio |
|---|---|---|
length() |
Ottiene la lunghezza | length(people) |
sort() |
Ordina i valori | sort(people[*].age) |
sort_by() |
Ordina per un campo | sort_by(people, &age) |
max() / min() |
Massimo / minimo | max(people[*].age) |
sum() |
Somma i valori | sum(people[*].age) |
keys() |
Ottiene le chiavi dell'oggetto | keys(people[0]) |
contains() |
Verifica il contenimento | contains(name, "Ali") |
to_string() |
Converte in stringa | to_string(age) |
Il secondo argomento di sort_by è un'espressione &, che significa "prendi questo campo da ogni elemento":
sort_by(people, &age)[*].name
Queste funzioni permettono a JMESPath di fare semplici aggregazioni, mentre JSONPath è progettato solo per la selezione; sommare e raggruppare di solito richiede jq o codice.
6. Un confronto fianco a fianco con JSONPath
| Dimensione | JSONPath | JMESPath |
|---|---|---|
| Obiettivo principale | Selezionare nodi | Trasformare i dati |
| Simbolo di radice | $ |
Nessuno; scrivi direttamente i nomi dei campi |
| Nodo corrente | @ |
@ (solo negli argomenti delle funzioni) |
| Filtro | [?(@.age > 18)] |
[?age > 18] |
| Wildcard | [*] |
[*] o [] |
| Costruire nuovi oggetti | Non supportato | {name: name} |
| Pipe | Non supportate | | |
| Standard | RFC 9535 | JMESPath Specification |
Una guida decisionale in una frase:
- Usa JSONPath quando devi solo trovare nodi che corrispondono a una condizione;
- Usa JMESPath quando devi rimodellare il risultato in ciò che il frontend si aspetta;
- Non usare nessuno dei due quando leggi un campo noto; accedi direttamente alla proprietà.
Se il risultato della query deve essere riscritto nel documento, nessuno dei due linguaggi è appropriato. Usa invece JSON Pointer e le patch; vedi JSON Pointer, Patch e Merge Patch.
7. JMESPath in AWS CLI
Il parametro --query di AWS CLI è JMESPath, che è il suo caso d'uso più diffuso:
aws ec2 describe-instances \
--query 'Reservations[].Instances[].{Id: InstanceId, State: State.Name, Type: InstanceType}' \
--output table
Questo comando fa tre cose: appiattisce due liste annidate con [], mantiene solo tre campi con un hash multi-selezione, e renderizza il risultato come tabella con --output table. Con JSONPath avresti bisogno di più livelli e non otterresti l'effetto "rinomina più rifila".
Altre forme comuni:
# Only running instances
aws ec2 describe-instances --query 'Reservations[].Instances[?State.Name==`running`].[InstanceId,PublicIpAddress]'
# The value of the Name tag
aws ec2 describe-instances --query 'Reservations[].Instances[].Tags[?Key==`Name`].Value[]'
Nota che i backtick racchiudono i letterali, non le stringhe. In AWS CLI, le stringhe usano le virgolette singole e i letterali JSON usano i backtick. Questa differenza è uno degli errori più comuni dei principianti.
8. Errori comuni
- Confondere
[*]e[]: il primo preserva l'annidamento, il secondo appiattisce un livello, e le strutture risultanti sono completamente diverse; - Dimenticare la pipe:
people[*].name[0]prende il primo carattere di ogni nome, non il primo nome; - Scrivere
@dentro un filtro: i filtri JMESPath usano direttamente i nomi dei campi, quindi[?@.age > 18]è sbagliato; - Trattare
nullcome una stringa vuota: le proiezioni eliminanonull, ma gli hash multi-selezione mantengono la chiave; - Usare virgolette doppie attorno ai letterali in AWS CLI: la shell le espande prima e riscrive l'espressione.
Consiglio di debug: valida un'espressione sull'input più piccolo possibile prima di applicarla ai dati reali, e aggiungi --output json in AWS CLI per vedere i risultati grezzi. Per l'editing e la formattazione quotidiana di JSON, abbinare a Editor JSON e strumenti da riga di comando rende molto più rapida la localizzazione dei percorsi dei campi.
FAQ
JMESPath e JSONPath possono essere usati insieme?
Sì, ma di solito non è necessario. Un approccio comune è rifilare un piccolo oggetto con JMESPath, poi usare l'accesso ordinario alle proprietà nel codice. Se devi davvero localizzare per percorso e poi modificare, JSONPath legge e JSON Pointer scrive, che è una divisione del lavoro più chiara.
Perché people[*].name restituisce qualcosa di diverso da una singola stringa?
Perché [*] è una proiezione, e il risultato è sempre una lista, anche quando c'è una sola persona. Per ottenere un singolo valore, aggiungi una pipe e un indice: people[*].name | [0].
Cosa succede a null dentro una proiezione?
Una proiezione di lista salta automaticamente gli elementi che valutano a null, quindi il risultato può essere più corto della lista originale. Un hash multi-selezione mantiene la chiave e preserva il valore null, il che conta quando si costruiscono risposte API.
JMESPath può fare aggregazione e raggruppamento?
Può fare aggregazione di base con sum(), max() e sort_by(). Raggruppamento, join e window function sono fuori dallo standard. Lascia l'analisi complessa a jq, SQL o codice.
Come decido tra JMESPath e JSONPath?
Usa JSONPath quando devi selezionare nodi per percorso e condizione e ricevere un insieme di nodi; usa JMESPath quando devi rimodellare il risultato in ciò che il frontend si aspetta, con ridenominazione e rifinitura dei campi. Quando leggi un campo noto, non usare nessuno dei due; accedi direttamente alla proprietà.
Devo usare una pipe per prendere il primo valore proiettato?
Sì. people[*].name è una proiezione, quindi il risultato è sempre una lista; senza pipe, people[*].name[0] prende il primo carattere di ogni nome. Scrivi people[*].name | [0] per ottenere il primo valore.