Guida alle query JSONPath: come cercare dati JSON
JSON è diventato la lingua universale del Web. Le API restituiscono JSON, i file di configurazione usano JSON, i database archiviano documenti JSON. Ma man mano che le strutture JSON diventano più grandi e più profondamente annidate, trovare valori specifici diventa sempre più difficile. È qui che entra in gioco JSONPath. JSONPath è un linguaggio di query che ti consente di navigare ed estrarre dati da documenti JSON usando espressioni di percorso concise, proprio come XPath fa per XML. Questa guida copre tutto, dalla sintassi di base ai filtri avanzati, con esempi pratici che puoi applicare immediatamente.
Cos'è JSONPath?
JSONPath è un linguaggio di query JSON originariamente proposto da Stefan Goessner nel 2007. Fornisce una sintassi compatta per selezionare nodi da documenti JSON, simile a come i selettori CSS individuano gli elementi HTML o le espressioni XPath individuano i nodi XML. Invece di scrivere cicli e logica condizionale per attraversare le strutture JSON, scrivi semplicemente un'espressione che descrive il percorso verso i dati desiderati.
Le espressioni JSONPath iniziano dal nodo radice del documento JSON e navigano attraverso oggetti e array fino ai valori desiderati. Il linguaggio supporta jolly, discesa ricorsiva, sezionamento di array ed espressioni di filtro, sufficienti per la maggior parte delle esigenze di estrazione dati.
Documento JSON di esempio
In questa guida, utilizzeremo il seguente documento JSON come esempio di lavoro. Questo è l'esempio classico della proposta originale di JSONPath, con dati aggiuntivi:
{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J.R.R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
}
}Riferimento alla sintassi JSONPath
JSONPath utilizza un piccolo insieme di operatori che si combinano per formare query potenti. Ecco il riferimento completo alla sintassi:
| Operatore | Descrizione | Esempio |
|---|---|---|
| $ | Nodo radice del documento | $ |
| . | Operatore figlio (accede alle proprietà dell'oggetto) | $.store |
| [] | Indice array o operatore figlio | $.store.book[0] |
| [*] | Jolly per tutti gli elementi dell'array | $.store.book[*] |
| .. | Discesa ricorsiva (cerca a tutti i livelli) | $..author |
| .key | Proprietà oggetto con nome | $.store.bicycle |
| ['key'] | Notazione a parentesi per l'accesso alle proprietà | $['store']['book'] |
| [start:end] | Sezionamento array (indice finale escluso) | $.store.book[0:2] |
| [?()] | Espressione di filtro | < 10)] |
| () | Espressione di script (dipende dall'implementazione) | $.store.book[(@.length-1)] |
Espressioni JSONPath di base
Accesso al nodo radice e ai figli diretti
Il simbolo del dollaro $ rappresenta il nodo radice del documento JSON. Da lì, usi la notazione a punto per accedere alle proprietà degli oggetti e la notazione a parentesi per accedere agli indici degli array.
| Espressione | Risultato |
|---|---|
| $ | L'intero documento JSON |
| $.store | L'oggetto store (contiene l'array book e l'oggetto bicycle) |
| $.store.bicycle | {"color": "red", "price": 19.95} |
| $.store.bicycle.color | "red" |
| $.store.book | L'intero array book |
| $.store.book[0] | Il primo oggetto libro |
| $.store.book[0].title | "Sayings of the Century" |
| $.store.book[3].author | "J.R.R. Tolkien" |
Notazione a parentesi
La notazione a parentesi è un'alternativa alla notazione a punto, utile quando i nomi delle proprietà contengono caratteri speciali, spazi o iniziano con un numero:
$.store['book'][0]['title']
$['store']['bicycle']['color']La notazione a parentesi e la notazione a punto sono intercambiabili per le proprietà degli oggetti. Tuttavia, quando il nome della proprietà è dinamico o contiene caratteri non validi nella notazione a punto, devi usare la notazione a parentesi.
Operatore jolly
L'operatore jolly * corrisponde a tutti gli elementi di un array o a tutte le proprietà di un oggetto. È estremamente utile per estrarre tutti i valori a un certo livello senza conoscere le chiavi o gli indici specifici.
| Espressione | Risultato |
|---|---|
| $.store.* | Tutti i valori nell'oggetto store (array book e oggetto bicycle) |
| $.store.book[*] | Tutti i libri nell'array (tutti e quattro gli oggetti libro) |
| $.store.book[*].author | Tutti i nomi degli autori: ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J.R.R. Tolkien"] |
| $.store.*.price | Il prezzo di bicycle e l'array book (non i prezzi dei singoli libri) |
Discesa ricorsiva: l'operatore doppio punto
L'operatore doppio punto .. è una delle funzionalità più potenti di JSONPath. Cerca la chiave specificata a ogni livello dell'albero JSON, non solo nei figli diretti. Pensalo come una ricerca approfondita nell'intero documento.
| Espressione | Risultato |
|---|---|
| $..author | Tutti i valori author ovunque nel documento |
| $..price | Tutti i valori price: [8.95, 12.99, 8.99, 22.99, 19.95] |
| $..book[2] | Il terzo libro, trovato ricorsivamente |
| $..category | Tutti i valori category: ["reference", "fiction", "fiction", "fiction"] |
L'operatore di discesa ricorsiva è particolarmente utile quando non conosci il percorso esatto dei dati desiderati, o quando la stessa chiave appare a più livelli di annidamento. Ad esempio, se una risposta API contiene campi id a vari livelli di annidamento, $..id raccoglierà tutti gli id.
Sezionamento di array
Il sezionamento di array ti consente di selezionare un intervallo di elementi da un array. La sintassi [start:end] seleziona gli elementi dall'indice iniziale fino a (ma non incluso) l'indice finale. Sono supportati sia indici positivi che negativi.
| Espressione | Risultato |
|---|---|
| $.store.book[0:2] | I primi due libri (indici 0 e 1) |
| $.store.book[1:3] | Il secondo e il terzo libro (indici 1 e 2) |
| $.store.book[-1] | L'ultimo libro (The Lord of the Rings) |
| $.store.book[-2:] | Gli ultimi due libri |
| $.store.book[:2] | I primi due libri (equivalente a [0:2]) |
| $.store.book[2:] | Il terzo libro e successivi |
Nota che il comportamento del sezionamento può variare leggermente tra le diverse implementazioni di JSONPath. La sintassi sopra segue le convenzioni utilizzate dalla maggior parte delle librerie popolari e dallo standard IETF RFC 9535.
Espressioni di filtro
Le espressioni di filtro sono dove JSONPath diventa veramente potente. Ti consentono di selezionare elementi in base a condizioni piuttosto che alla posizione. La sintassi di filtro usa [?(condition)], dove la condizione viene valutata per ogni elemento.
Operatori di confronto
JSONPath supporta i seguenti operatori di confronto nelle espressioni di filtro:
| Operatore | Significato | Esempio |
|---|---|---|
| == | Uguale a | [?(@.category == "fiction")] |
| != | Diverso da | [?(@.category != "fiction")] |
| < | Minore di | < 10)] |
| <= | Minore o uguale a | <= 8.99)] |
| > | Maggiore di | [?(@.price > 15)] |
| >= | Maggiore o uguale a | [?(@.price >= 12.99)] |
| =~ | Corrispondenza regex (implementazione parziale) | [?(@.author =~ /Tolkien/i)] |
Esempi pratici di filtro
Utilizzando il nostro documento di esempio, ecco espressioni di filtro pratiche e i loro risultati:
Find all books cheaper than $10:
< 10)]Restituisce i libri "Sayings of the Century" ($8.95) e "Moby Dick" ($8.99).
Find all fiction books:
$.store.book[?(@.category == "fiction")]Restituisce tre libri: "Sword of Honour", "Moby Dick" e "The Lord of the Rings".
Find books with an ISBN:
$.store.book[?(@.isbn)]Restituisce i libri che hanno una proprietà isbn: "Moby Dick" e "The Lord of the Rings". Questo perché il filtro verifica l'esistenza della proprietà.
Find the most expensive book:
$.store.book[?(@.price > 20)]Restituisce "The Lord of the Rings" ($22.99).
Operatori logici nei filtri
Puoi combinare condizioni usando operatori logici:
< 10 && @.category == "fiction")]
$.store.book[?(@.price > 15 || @.category == "reference")]La prima espressione trova libri di narrativa sotto i $10 (solo "Moby Dick"). La seconda trova libri sopra i $15 o nella categoria reference (restituisce "Sayings of the Century" e "The Lord of the Rings").
JSONPath vs XPath
JSONPath è esplicitamente modellato su XPath, e i due linguaggi condividono molte somiglianze concettuali. Se hai familiarità con l'elaborazione XML, comprendere questa relazione è utile.
| Funzionalità | JSONPath | XPath |
|---|---|---|
| Simbolo radice | $ | / |
| Accesso ai figli | .key o ['key'] | /element |
| Indice array | [0] | [1] (a base 1) |
| Jolly | * | * |
| Discesa ricorsiva | .. | // |
| Filtro | [?(condition)] | [condition] |
| Attributo | N/D (JSON non ha attributi) | @attr |
| Nodo corrente | @ (nei filtri) | . o current() |
| Genitore | Non supportato | .. |
| Assi | Non supportati | 13 assi (ancestor, following, ecc.) |
| Modello dati | Oggetti e array | Elementi, attributi, nodi di testo |
La differenza chiave è che XPath opera su un ricco modello ad albero con elementi, attributi, nodi di testo, namespace e istruzioni di elaborazione. JSONPath opera su un modello più semplice di oggetti (mappe chiave-valore) e array (liste ordinate). Questa semplicità rende JSONPath più facile da imparare ma meno espressivo di XPath per query complesse.
JSONPath in pratica
Test API
JSONPath è indispensabile per i test API. Quando invii una richiesta a un'API e ricevi una grande risposta JSON, JSONPath ti consente di asserire valori specifici senza navigare manualmente l'intera struttura. La maggior parte degli strumenti di test API supporta nativamente JSONPath.
Ad esempio, in un test potresti verificare che il prezzo del primo libro sia inferiore a $10:
// Using a JSONPath assertion in testing
response.jsonPath().get("store.book[0].price").should(equals(8.95));
// Find all books by a specific author
response.jsonPath().get("store.book[?(@.author == 'Herman Melville')].title");
// Returns: ["Moby Dick"]Trasformazione dati
Quando integri sistemi che utilizzano formati di dati diversi, JSONPath aiuta a estrarre e trasformare campi specifici. Puoi estrarre valori da una struttura JSON e mapparli in un'altra senza scrivere codice di attraversamento complesso.
Gestione della configurazione
I file di configurazione complessi spesso contengono JSON profondamente annidato. JSONPath ti consente di interrogare valori di configurazione specifici senza caricare e analizzare l'intera struttura. Strumenti come jq utilizzano una sintassi simile a JSONPath per elaborare JSON nelle pipeline a riga di comando.
Monitoraggio e alerting
Nei sistemi di osservabilità, le query JSONPath possono estrarre metriche da log e risposte API in formato JSON. Puoi impostare avvisi che si attivano quando una query JSONPath restituisce un valore che supera una soglia.
Implementazioni JSONPath
JSONPath è disponibile in quasi tutti i linguaggi di programmazione. Ecco le librerie più popolari:
| Linguaggio | Libreria | Installazione |
|---|---|---|
| JavaScript | jsonpath-plus | npm install jsonpath-plus |
| Python | jsonpath-ng | pip install jsonpath-ng |
| Java | JsonPath (Jayway) | Maven: com.jayway.jsonpath |
| C# | Json.NET (Newtonsoft) | NuGet: Newtonsoft.Json |
| Go | gjson | go get github.com/tidwall/gjson |
| PHP | jsonpath | composer require softcreatr/jsonpath |
| Ruby | jsonpath | gem install jsonpath |
Esempio JavaScript
< 10)]',
json: data
});
// Returns books with price < 10
Python Example
from jsonpath_ng import parse
data = { /* our sample JSON */ }
# Find all authors
author_expr = parse('$..author')
authors = [match.value for match in author_expr.find(data)]
# ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J.R.R. Tolkien"]
# Find cheap books
cheap_expr = parse('$.store.book[?(@.price < 10)]')
cheap_books = [match.value for match in cheap_expr.find(data)]
Common Pitfalls and Tips
- Zero-based vs one-based indexing: JSONPath uses zero-based indexing (the first element is [0]), while XPath uses one-based indexing (the first element is [1]). This is a common source of off-by-one errors.
- Implementation differences: Before the IETF standard (RFC 9535), JSONPath implementations varied in their handling of edge cases like empty results, null values, and filter syntax. Always test your expressions with the specific library you are using.
- Filter performance: Recursive descent with filters (
$..book[?(@.price < 10)]) can be slow on large documents because it must traverse the entire tree. For performance-critical applications, use more specific paths when possible. - Case sensitivity: JSONPath is case-sensitive.
$.Storewill not match$.store. This is consistent with JSON's case-sensitive nature. - Indicizzazione a base zero vs a base uno: JSONPath usa l'indicizzazione a base zero (il primo elemento è [0]), mentre XPath usa l'indicizzazione a base uno (il primo elemento è [1]). Questa è una fonte comune di errori off-by-one.
- Differenze di implementazione: prima dello standard IETF (RFC 9535), le implementazioni JSONPath differivano nella gestione di casi limite come risultati vuoti, valori null e sintassi di filtro. Testa sempre le espressioni con la tua libreria specifica.
Insidie e consigli comuni
Nel 2024, l'IETF ha pubblicato RFC 9535, standardizzando formalmente JSONPath. La specifica risolve molte ambiguità e incoerenze che esistevano tra le implementazioni. Gli aspetti chiave dello standard includono:
- < 10)]) può essere lenta su documenti di grandi dimensioni perché deve attraversare l'intero albero. Per applicazioni critiche per le prestazioni, usa percorsi più specifici quando possibile.
- Sensibilità alle maiuscole: JSONPath è sensibile alle maiuscole. $.Store non corrisponderà a $.store. Questo è coerente con la natura case-sensitive di JSON.
- Escape dei caratteri speciali: se i nomi delle chiavi contengono punti o parentesi, devi usare la notazione a parentesi con virgolette: $['key.with.dots'] invece di $.key.with.dots.
- Nessun attraversamento al genitore: a differenza di XPath, JSONPath non può navigare al nodo genitore. Non esiste un equivalente all'asse .. (genitore) di XPath. Il .. di JSONPath significa discesa ricorsiva, non genitore.
Se stai iniziando un nuovo progetto, preferisci librerie che implementano RFC 9535 per la massima compatibilità e comportamento prevedibile.
Hai bisogno di interrogare rapidamente dati JSON? Prova il nostro tester JSONPath online gratuito per valutare le espressioni in tempo reale sui tuoi documenti JSON.
Prova il cercatore JSONPathStandard IETF: RFC 9535
Esempio Python
JSONPath è un linguaggio di query JSON, simile a XPath per XML. Utilizza espressioni di percorso per navigare ed estrarre valori specifici dai documenti JSON. Un'espressione JSONPath come $.store.book[0].title ti consente di individuare dati in complesse strutture JSON annidate senza scrivere codice di parsing personalizzato.
Cos'è JSONPath?
JSONPath è progettato per la struttura a oggetti e array di JSON, mentre XPath è progettato per l'albero di elementi e attributi di XML. JSONPath usa $ come nodo radice, la notazione a punto per accedere agli oggetti e la notazione a parentesi per accedere agli array. XPath usa / per la separazione del percorso e @ per accedere agli attributi. JSONPath è più semplice ma meno ricco di funzionalità di XPath.
In cosa JSONPath differisce da XPath?
Il doppio punto (..) in JSONPath è l'operatore di discesa ricorsiva. Cerca la chiave nominata a tutti i livelli della struttura JSON, non solo nei figli diretti. Ad esempio, $..author trova tutte le chiavi author ovunque nel documento, indipendentemente da quanto profondamente siano annidate.
Cosa significa il doppio punto (..) in JSONPath?
<, ><=, >< 10)] trova tutti i libri sotto i $10.
JSONPath può filtrare i dati in base a condizioni?
JSONPath è stato originariamente proposto da Stefan Goessner nel 2007 senza una specifica formale, portando a variazioni tra le implementazioni. Nel 2024, l'IETF ha pubblicato RFC 9535 standardizzando formalmente JSONPath. Le implementazioni moderne stanno convergendo su questo standard, ma alcune librerie più vecchie potrebbero ancora avere lievi differenze di sintassi.