Se hai mai fissato una risposta API profondamente annidata pensando "ho solo bisogno di quel campo sepolto là dentro" — JSONPath è lo strumento che stai cercando. È un linguaggio di query per JSON, simile per concetto a come XPath funziona per XML. Scrivi un'espressione di percorso, ottieni i valori corrispondenti. Nessun ciclo, nessuna traversata manuale. Stefan Goessner lo ha introdotto nel 2007, e dopo anni di implementazioni leggermente incompatibili in circolazione, è stato formalmente standardizzato come RFC 9535 nel 2024. Puoi anche esplorare le espressioni JSONPath in modo interattivo con lo strumento JSON Path.
Il Dataset che Useremo
Invece di esempi astratti, lavoriamo con qualcosa di realistico — una risposta di ordine e-commerce. Questo è il tipo di JSON che otterresti da un'API di gestione ordini:
{
"order": {
"id": "ORD-9182",
"status": "shipped",
"customer": {
"id": "CUST-441",
"name": "Maria Chen",
"email": "[email protected]"
},
"shippingAddress": {
"street": "14 Maple Avenue",
"city": "Portland",
"state": "OR",
"zip": "97201",
"country": "US"
},
"lineItems": [
{
"sku": "WH-1042",
"name": "Wireless Headphones",
"qty": 1,
"unitPrice": 89.99,
"inStock": true
},
{
"sku": "CB-USB-C",
"name": "USB-C Charging Cable",
"qty": 2,
"unitPrice": 12.49,
"inStock": true
},
{
"sku": "SC-PRO-7",
"name": "Phone Stand Pro",
"qty": 1,
"unitPrice": 34.00,
"inStock": false
}
],
"totals": {
"subtotal": 148.97,
"shipping": 5.99,
"tax": 11.92,
"total": 166.88
}
}
}L'Operatore Root $ e la Navigazione Base
Ogni espressione JSONPath inizia con $, che fa riferimento alla radice del documento.
Da lì, si naviga usando i punti. Vuoi lo stato dell'ordine? È semplice:
$.order.status
// → "shipped"
$.order.customer.name
// → "Maria Chen"
$.order.totals.total
// → 166.88
$.order.shippingAddress.city
// → "Portland"Puoi anche usare la notazione a parentesi, utile quando una chiave contiene spazi o caratteri speciali, o quando stai accedendo agli elementi di un array per indice. Entrambi gli stili sono validi secondo le regole di sintassi di RFC 9535:
// Dot notation
$.order.customer.email
// Bracket notation — equivalent
$['order']['customer']['email']
// Array index — first line item
$.order.lineItems[0].name
// → "Wireless Headphones"
// Last line item (negative indexing — supported in RFC 9535)
$.order.lineItems[-1].name
// → "Phone Stand Pro"$[-1:] (sintassi slice) e
$.array[-1] (indice negativo diretto) sono entrambi validi in RFC 9535, ma alcune implementazioni
più vecchie non li supportano. Testa con la tua libreria specifica se stai usando indici negativi.Wildcard — Interroga Tutti i Figli in una Volta
Il carattere jolly * corrisponde a tutti gli elementi a un dato livello. Invece di chiedere
lineItems[0], lineItems[1], ecc., puoi prendere tutto in una volta:
// All line item names
$.order.lineItems[*].name
// → ["Wireless Headphones", "USB-C Charging Cable", "Phone Stand Pro"]
// All line item unit prices
$.order.lineItems[*].unitPrice
// → [89.99, 12.49, 34.00]
// All line item SKUs
$.order.lineItems[*].sku
// → ["WH-1042", "CB-USB-C", "SC-PRO-7"]Questo è il pattern che userai più spesso quando lavori con gli array nelle risposte API. Invece di mappare su un array nel codice dell'applicazione, puoi estrarre esattamente il campo di cui hai bisogno prima che raggiunga la tua logica di business.
Discesa Ricorsiva — Trovare Valori a Qualsiasi Profondità
L'operatore .. esegue una ricerca ricorsiva nell'intero albero del documento.
È come una visita in profondità che raccoglie ogni nodo corrispondente al nome della chiave, indipendentemente da dove si trova:
// Find every "name" field anywhere in the document
$..name
// → ["Maria Chen", "Wireless Headphones", "USB-C Charging Cable", "Phone Stand Pro"]
// Find every "city" field anywhere
$..city
// → ["Portland"]
// Find every field called "id" at any depth
$..id
// → ["ORD-9182", "CUST-441"]Nota come $..name trova sia il nome del cliente che tutti i nomi dei prodotti —
non si preoccupa della profondità. Questo è potente per l'esplorazione dello schema: quando ti viene consegnato un
blob JSON sconosciuto e vuoi trovare tutti i valori per una particolare chiave senza conoscere la struttura.
Se vuoi verificare prima il JSON stesso, il Validatore JSON
e il Formattatore JSON sono ottimi punti di partenza.
Slicing degli Array
JSONPath prende in prestito la notazione slice in stile Python per gli array. Il formato è
[start:end:step], dove qualsiasi parte può essere omessa.
Questo è documentato in RFC 9535 § 2.3.5:
// First two line items
$.order.lineItems[0:2]
// → [{ sku: "WH-1042", ... }, { sku: "CB-USB-C", ... }]
// From index 1 to end
$.order.lineItems[1:]
// → [{ sku: "CB-USB-C", ... }, { sku: "SC-PRO-7", ... }]
// Last item only (slice syntax — widely supported)
$.order.lineItems[-1:]
// → [{ sku: "SC-PRO-7", ... }]
// Every other item (step of 2)
$.order.lineItems[0::2]
// → [{ sku: "WH-1042", ... }, { sku: "SC-PRO-7", ... }]Espressioni di Filtro — Interrogare per Condizione
Qui JSONPath dimostra il suo valore. La sintassi di filtro [?(...)]
ti permette di interrogare gli elementi dell'array in base ai valori dei loro campi. Il simbolo @ fa riferimento all'elemento
corrente in fase di test. L'
articolo originale del 2007 di Stefan Goessner
ha introdotto questa sintassi, e RFC 9535 l'ha formalizzata:
// Line items under $50
$.order.lineItems[?(@.unitPrice < 50)]
// → [{ sku: "CB-USB-C", unitPrice: 12.49, ... }, { sku: "SC-PRO-7", unitPrice: 34.00, ... }]
// Only items currently in stock
$.order.lineItems[?(@.inStock == true)]
// → [{ sku: "WH-1042", ... }, { sku: "CB-USB-C", ... }]
// Items where qty is greater than 1
$.order.lineItems[?(@.qty > 1)]
// → [{ sku: "CB-USB-C", qty: 2, ... }]
// Items that are NOT in stock
$.order.lineItems[?(@.inStock == false)].name
// → ["Phone Stand Pro"]Puoi anche combinare le condizioni. La maggior parte delle implementazioni supporta && e ||
all'interno delle espressioni di filtro, permettendoti di scrivere cose come
[?(@.inStock == true && @.unitPrice < 30)].
Controlla la documentazione della tua libreria — il comportamento riguardo al supporto degli operatori era una delle principali incongruenze
tra le implementazioni pre-RFC che la
standardizzazione RFC 9535 mirava a risolvere.
[?(@.fieldName)]. Ad esempio,
se alcuni elementi avessero un campo discountCode e altri no,
$.order.lineItems[?(@.discountCode)] restituirebbe solo quelli scontati.Usare JSONPath in JavaScript
Non c'è ancora supporto JSONPath nativo in JavaScript (a differenza di XPath, che ha
document.evaluate() nel browser). Avrai bisogno di una libreria.
Le due più popolari sono jsonpath
e jsonpath-plus.
Il pacchetto jsonpath-plus è mantenuto più attivamente e ha un migliore allineamento con RFC 9535:
npm install jsonpath-plusimport { JSONPath } from 'jsonpath-plus';
const order = {
order: {
id: 'ORD-9182',
status: 'shipped',
customer: { name: 'Maria Chen', email: '[email protected]' },
lineItems: [
{ sku: 'WH-1042', name: 'Wireless Headphones', qty: 1, unitPrice: 89.99, inStock: true },
{ sku: 'CB-USB-C', name: 'USB-C Charging Cable', qty: 2, unitPrice: 12.49, inStock: true },
{ sku: 'SC-PRO-7', name: 'Phone Stand Pro', qty: 1, unitPrice: 34.00, inStock: false }
],
totals: { subtotal: 148.97, shipping: 5.99, tax: 11.92, total: 166.88 }
}
};
// Get all product names
const names = JSONPath({ path: '$.order.lineItems[*].name', json: order });
console.log(names);
// [ 'Wireless Headphones', 'USB-C Charging Cable', 'Phone Stand Pro' ]
// Get in-stock items under $50
const affordable = JSONPath({ path: '$.order.lineItems[?(@.inStock == true && @.unitPrice < 50)]', json: order });
console.log(affordable.map(item => item.name));
// [ 'USB-C Charging Cable' ]
// Get the order total
const total = JSONPath({ path: '$.order.totals.total', json: order });
console.log(total[0]); // 166.88Nota che JSONPath() restituisce sempre un array — anche quando stai interrogando un singolo valore.
È per design: un'espressione di percorso è un selettore, e un selettore può corrispondere a zero, uno o molti nodi.
Quindi prendi sempre result[0] quando sai di stare puntando a un valore univoco.
Usare JSONPath in Python
In Python, la libreria
jsonpath-ng
è l'opzione più completa. Supporta la spec principale più gran parte della sintassi delle espressioni di filtro:
pip install jsonpath-ngfrom jsonpath_ng import parse
order = {
"order": {
"id": "ORD-9182",
"status": "shipped",
"customer": {"name": "Maria Chen", "email": "[email protected]"},
"lineItems": [
{"sku": "WH-1042", "name": "Wireless Headphones", "qty": 1, "unitPrice": 89.99, "inStock": True},
{"sku": "CB-USB-C", "name": "USB-C Charging Cable", "qty": 2, "unitPrice": 12.49, "inStock": True},
{"sku": "SC-PRO-7", "name": "Phone Stand Pro", "qty": 1, "unitPrice": 34.00, "inStock": False}
],
"totals": {"subtotal": 148.97, "shipping": 5.99, "tax": 11.92, "total": 166.88}
}
}
# Parse the expression once, then reuse it (more efficient)
expr = parse("$.order.lineItems[*].name")
names = [match.value for match in expr.find(order)]
print(names)
# ['Wireless Headphones', 'USB-C Charging Cable', 'Phone Stand Pro']
# Get all line items and check stock
expr2 = parse("$.order.lineItems[*]")
for item in [m.value for m in expr2.find(order)]:
status = "in stock" if item["inStock"] else "OUT OF STOCK"
print(f"{item['name']} (${item['unitPrice']}) — {status}")
# Output:
# Wireless Headphones ($89.99) — in stock
# USB-C Charging Cable ($12.49) — in stock
# Phone Stand Pro ($34.0) — OUT OF STOCKIl pattern parse() + find() è l'approccio giusto per l'uso in produzione —
compila l'espressione una volta e applicala a molti documenti, invece di effettuare il parsing della stringa di percorso ogni volta.
La documentazione di jsonpath-ng su GitHub
ha maggiori dettagli sulla sintassi dei filtri estesi e il metodo update() per modificare i nodi corrispondenti.
JSONPath vs jq — Quale Scegliere?
A volte si confondono JSONPath e jq — entrambi "interrogano JSON", ma risolvono problemi diversi. Ecco il confronto pratico:
- JSONPath è un linguaggio di espressioni di percorso progettato per essere incorporato nelle applicazioni. Interroga ed estrae valori. Lo usi all'interno di codice JavaScript, Python, Java o file di configurazione come i selettori di Kubernetes.
- jq è un processore da riga di comando completo con il proprio linguaggio di programmazione. Può interrogare, trasformare, rimodellare, filtrare e calcolare nuovi valori. È lo strumento giusto per gli script di shell e la manipolazione dei dati occasionale in un terminale.
- Usa JSONPath quando stai scrivendo codice applicativo che deve estrarre campi da JSON in modo programmatico — specialmente quando la libreria deve essere incorporabile e leggera.
- Usa jq quando sei alla riga di comando, scrivi script di shell, effettui il debug di risposte API o hai bisogno di trasformazioni (rinominare campi, aggregare, costruire nuove strutture) che vanno oltre la semplice estrazione.
- La sintassi jq è più potente ma anche più complessa —
jq '.order.lineItems[] | select(.inStock) | .name'vs$.order.lineItems[?(@.inStock)].namedi JSONPath. Per la pura estrazione, JSONPath è spesso più leggibile.
C'è anche una via di mezzo: se stai già lavorando in JavaScript e hai bisogno solo di trasformazioni occasionali,
qualcosa come _.get() di Lodash
copre l'accesso semplice ai percorsi senza dipendenze. Ma per qualsiasi cosa che coinvolga wildcard, ricorsione,
o espressioni di filtro, una libreria JSONPath dedicata vale la pena.
Conclusioni
JSONPath colma un vero divario tra "traversa manualmente questo oggetto in un ciclo for" e
"avvia un processo jq". Una volta che ti senti a tuo agio con $, ., [*],
.. e [?(filter)], ti ritroverai a usarlo costantemente —
specialmente quando lavori con payload API di grandi dimensioni dove hai bisogno solo di una manciata di campi.
La standardizzazione RFC 9535
significa che la sintassi è ora stabile, quindi le espressioni che scrivi oggi dovrebbero funzionare in modo coerente tra
le librerie conformi.
Prova le tue espressioni con lo strumento JSON Path — incolla il tuo JSON,
scrivi un percorso e vedi i risultati immediatamente. E se il tuo JSON ha bisogno di essere ripulito prima,
il Formattatore JSON e il Validatore JSON
sono lì per te.