Tester JSONPath

Testa query JSONPath (RFC 9535) su JSON: selettori, filtri, tutte e cinque le funzioni e il percorso normalizzato di ogni corrispondenza — nel browser.

Query JSONPath
JSON
Corrispondenze: 2
  • Percorso$['store']['book'][0]['title']
    "Sayings of the Century"
  • Percorso$['store']['book'][2]['title']
    "Moby Dick"

Un linguaggio di query che finalmente ha uno standard

JSONPath sta a JSON come XPath sta a XML: un piccolo linguaggio per puntare a parti di un documento. Scrivi un'espressione come $.store.book[0].title e seleziona i nodi corrispondenti. L'idea viene da un post sul blog di Stefan Goessner del 2007, e per diciassette anni quel post è stato l'unico riferimento — il che ha fatto sì che ogni libreria colmasse le lacune in modo diverso. Cosa seleziona un $.. da solo? $[1,2] torna garantito in ordine? Come confronta un filtro un valore che non c'è? Chiedi a tre librerie JSONPath e potresti ottenere tre risposte.

RFC 9535, pubblicato nel 2024, ha finalmente fissato tutto. Questo tester punta a quello standard, non al folclore. Esegue la query come dice la RFC, ti mostra ogni corrispondenza con la sua posizione esatta e — cosa importante — rifiuta una query valida in un dialetto più vecchio ma non in RFC 9535, dicendoti dov'è il problema invece di fare in silenzio qualcosa di non standard che un altro strumento farebbe diversamente.

Segmenti e selettori

Una query è l'identificatore radice $ seguito da una sequenza di segmenti, e ogni segmento applica uno o più selettori ai nodi che riceve. Ci sono cinque selettori:

  • Nome — $.store o $["store"] seleziona il valore di un membro. La forma con il punto è un'abbreviazione; la forma con parentesi e virgolette funziona per qualsiasi chiave, comprese quelle con spazi o punteggiatura.
  • Jolly — * seleziona ogni membro di un oggetto o ogni elemento di un array.
  • Indice — [0] seleziona un elemento di un array, e un indice negativo conta dalla fine, quindi [-1] è l'ultimo.
  • Slice — [start:end:step] seleziona un intervallo, esattamente come in Python: [1:3] sono gli elementi 1 e 2, [::-1] inverte, [::2] prende uno ogni due.
  • Filtro — [?<espressione>] mantiene solo gli elementi o i membri per cui l'espressione è vera (spiegato più avanti).

Una parentesi può contenere più selettori insieme: [0, 2, "title"] seleziona tre cose in un solo segmento. E un segmento può essere un segmento figlio (un punto o una parentesi singola) o un segmento discendente (..), che cerca nel nodo e in tutti i suoi discendenti — $..author trova ogni author ovunque nel documento.

I filtri, e i quattro modi di confrontare il nulla

Un selettore di filtro verifica ogni elemento con un'espressione logica, in cui @ si riferisce all'elemento corrente e $ all'intero documento. L'espressione può confrontare valori (==, !=, <, <=, >, >=), combinare test con && e || e !, e semplicemente verificare l'esistenza: $..book[[email protected]] mantiene i libri che hanno un ISBN, perché @.isbn seleziona un nodo solo quando la chiave è presente.

La parte sottile è confrontare qualcosa che non c'è. Una query a sinistra o a destra di un confronto produce un valore o nulla. La RFC lo definisce con precisione: nulla è uguale a nulla, nulla non è uguale a nessun valore reale, e qualsiasi test d'ordine (<, >) contro nulla è semplicemente falso. Così @.price < 10 salta in silenzio un elemento che non ha price invece di dare errore — di solito ciò che vuoi, e sempre ciò che dice lo standard.

Le cinque funzioni

RFC 9535 aggiunge cinque funzioni che puoi chiamare dentro un filtro:

  • length() — la lunghezza di una stringa (contata in caratteri), un array o un oggetto.
  • count() — quanti nodi seleziona una query, così da poter filtrare per cardinalità: [?count(@.chapters) > 3].
  • value() — l'unico valore che una query seleziona, o nulla se ne seleziona zero o molti.
  • match() — se una stringa corrisponde interamente a un'espressione regolare.
  • search() — se un'espressione regolare si trova in un punto qualsiasi di una stringa.

Lo standard è rigoroso su come si usano, e questo tester lo impone in fase di parsing: length() prende esattamente un valore, quindi length(@.*) — una query che potrebbe selezionare molti nodi — è un errore di sintassi, non una query che si comporta male in silenzio. Allo stesso modo match() restituisce vero o falso, quindi scrivere match(@.a, "x") == true viene rifiutato, perché un risultato logico non è qualcosa che si confronta.

Le espressioni regolari sono I-Regexp

match() e search() non usano le espressioni regolari di JavaScript; usano I-Regexp (RFC 9485), un piccolo sottoinsieme portabile pensato per comportarsi allo stesso modo in tutti i linguaggi. La maggior parte è ciò che ti aspetti — classi di caratteri, quantificatori, alternanza, proprietà Unicode come \p{Lu} per una lettera maiuscola. L'unica insidia è il punto: in I-Regexp, . corrisponde a qualsiasi carattere tranne un ritorno a capo o un avanzamento di riga, il che significa che corrisponde ai separatori di riga Unicode U+2028 e U+2029 che un punto di JavaScript esclude. Questo tester compila I-Regexp fedelmente, così che un pattern si comporti qui come lo valuterebbe un server conforme.

La differenza tra le due funzioni è solo l'ancoraggio: match richiede che l'intera stringa corrisponda, mentre search cerca il pattern in un punto qualsiasi di essa. match(@, "a.*") accetta "abc"; search(@, "b") accetta qualsiasi stringa che contenga una b.

Percorsi normalizzati, ed esecuzione nel browser

Per ogni corrispondenza, questo tester mostra un Normalized Path — la posizione canonica che la RFC definisce, scritta nella forma con parentesi e virgolette: $['store']['book'][0]['author']. A differenza della query, che può selezionare molti nodi, un percorso normalizzato ne indica esattamente uno, usando solo selettori di nome e indice e uno stile di virgolette fisso. È la risposta a "da dove è venuta questa corrispondenza", ed è ciò che permette di riconvertire un risultato jolly in un insieme di posizioni concrete. La maggior parte dei tester mostra i valori e ti lascia dedurre i percorsi; questo mostra entrambi.

L'intero motore gira nel tuo browser — il JSON viene analizzato e la query valutata sul tuo dispositivo, e nulla di ciò che incolli viene caricato, memorizzato o registrato. È validato contro la suite ufficiale di test di conformità JSONPath, così le sue risposte concordano con lo standard e non con l'interpretazione di una singola libreria.

Domande frequenti

Quale dialetto JSONPath usa?
RFC 9535, lo standard IETF del 2024, ed è validato contro la suite ufficiale di test di conformità JSONPath. Deliberatamente non accetta le vecchie convenzioni di Goessner dove divergono dalla RFC; una query di questo tipo viene rifiutata con la posizione del problema così puoi correggerla.
Perché la mia query è stata rifiutata se funziona in un altro strumento?
Perché quello strumento segue le convenzioni di Goessner pre-standard, che differiscono da RFC 9535 in diversi punti — un $.. da solo, espressioni di script come [(@.length-1)], indici con zero iniziale e nomi non tra virgolette con caratteri speciali sono tutti non standard. La RFC li ha sostituiti con equivalenti ben definiti, e questo tester si attiene alla RFC.
Che cos'è un percorso normalizzato?
La posizione canonica di un singolo nodo, scritta nella forma con parentesi e virgolette che la RFC definisce, per es. $['store']['book'][0]['author']. Una query può corrispondere a molti nodi; ogni corrispondenza ha esattamente un percorso normalizzato, per questo il tester lo mostra accanto a ogni valore.
Come tratta un filtro un valore mancante?
Come nulla, con regole che la RFC fissa: nulla è uguale a nulla, nulla non è uguale a nessun valore reale, e qualsiasi confronto d'ordine (<, >) che coinvolge nulla è falso. Così @.price < 10 salta semplicemente un elemento senza price invece di sollevare un errore.
match() e search() usano le espressioni regolari di JavaScript?
No. Usano I-Regexp (RFC 9485), un sottoinsieme portabile. La principale differenza pratica è il punto, che corrisponde a tutto tranne ritorno a capo e avanzamento di riga — inclusi U+2028 e U+2029, che un punto di JavaScript esclude. Questo tester compila I-Regexp fedelmente così che i risultati corrispondano a un'implementazione conforme.
Qual è la differenza tra match e search?
L'ancoraggio. match() richiede che l'intera stringa corrisponda al pattern, come se fosse racchiusa tra àncore; search() riesce se il pattern si trova in un punto qualsiasi della stringa. Tutto il resto è identico.
Il mio JSON viene inviato a un server?
No. Il documento viene analizzato e la query valutata interamente nel tuo browser, e nulla di ciò che incolli viene caricato o registrato. È sicuro testare contro una vera risposta di un'API o un file di configurazione.