jq funziona facendo passare i dati JSON di input attraverso una singola espressione (scritta come una pipeline di filtri) per ottenere i dati trasformati desiderati.
jq
Il linguaggio jq è implementato dal programma jq.
Questo programma offre diverse comode opzioni della riga di comando per controllare come viene consumato l'input e come viene presentato l'output.
Negli esempi che seguono incontrerai:
-n o --null-input
Normalmente al programma jq viene dato un file da leggere, oppure gli si inviano dati in input.
L'opzione --null-input ti permette di generare dati JSON senza alcun input.
-c o --compact-output
Per impostazione predefinita, jq formatta l'output in modo leggibile.
È estremamente utile per gli esseri umani vedere i dati quando sono ben formattati.
Per le macchine, però, non è necessario: l'opzione --compact-output rimuove gli spazi bianchi di formattazione per ridurre al minimo la dimensione del JSON risultante.
-f filename o --from-file filename
Legge il programma jq da filename invece di fornirlo sulla riga di comando.
Sia sed che awk usano l'opzione -f per lo stesso scopo.
Lo vedrai usato negli script di test degli esercizi di pratica.
Vedi il manuale per i dettagli su tutte le opzioni.
I filtri sono anche noti come espressioni.
Un filtro prende un input e produce un output.
Come quando lavori in una shell unix, puoi unire i filtri con una pipe | per collegare l'output di un filtro all'input di un altro.
.
Questo è il filtro più semplice.
Si limita a passare il suo input al suo output.
Per esempio, jq formatta l'output in modo leggibile per impostazione predefinita, quindi passare del JSON al filtro . ti offre gratuitamente un output ben formattato!
$ echo '[1, 2, 3]' | jq '.'
[
1,
2,
3
]
Questa sarà una rapida introduzione a lavorare con gli array. Tratteremo questo argomento in modo più approfondito più avanti.
Gli elementi di un array si accedono con le parentesi quadre e hanno indici che partono da zero.
$ echo '[10, 20, 30]' | jq '.[1]'
20
Un filtro può costruire un array racchiudendo un'espressione tra [ e ]
con un elenco noto di elementi:
jq -n '[1, 2, 3]'
per raccogliere un flusso di elementi: per esempio,
range è una funzione che produce un flusso di numeri
$ jq -n 'range(10; 70; 15)'
10
25
40
55
Usare [] raccoglie i risultati dell'espressione in un array
$ jq -c -n '[range(10; 70; 15)]'
[10,25,40,55]
La virgola non è solo una sintassi che separa gli elementi di un array. La virgola è un operatore che unisce i flussi.
Per esempio, [1, 2, 3] è un filtro che usa il costruttore di array [] per raccogliere il risultato dell'unione delle tre espressioni 1, 2 e 3.
Hai notato i punti e virgola in range(10; 70; 15) più sopra?
Dato che le virgole hanno uno scopo specifico nel linguaggio jq, le funzioni che accettano più argomenti usano i punti e virgola per separare gli argomenti.
Una rapida introduzione agli oggetti.
Come in molti linguaggi di programmazione, usa i punti per accedere alle proprietà di un oggetto
$ echo '{"foo": {"bar": "qux"}}' | jq '.foo.bar'
"qux"
Le parentesi quadre si possono usare anche per gli oggetti, ma in quel caso servono le virgolette per le stringhe letterali. Questo è un metodo per lavorare con chiavi che contengono spazi.
$ echo '{"foo bar": "qux"}' | jq '.["foo bar"]'
"qux"
Puoi costruire un oggetto con {} e coppie key: value.
Le virgolette non sono necessarie attorno alle chiavi che sono stringhe «semplici».
jq -n '{question: (6 * 9), answer: 42}'
produce
{
"question": 54,
"answer": 42
}
Per trattare la chiave come un'espressione, devi racchiuderla tra parentesi (anche il codice seguente produce lo stesso output di sopra).
echo '[["question", "answer"], [54, 42]]' \
| jq '{(.[0][0]): .[1][0], (.[0][1]): .[1][1]}'
È molto comune voler estrarre un sottoinsieme di chiavi da un oggetto grande.
Per esempio, per estrarre id e name da
{
"id": 101,
"name": "alpha widget",
"specifications": {...}
}
Potremmo scrivere
{id: .id, name: .name}
Ma è così comune che esiste una sintassi abbreviata:
{id, name}
Per esempio, dato un file.json che contiene
{
"key1": "value1",
"key2": [5, 15, 25]
}
Calcoliamo la lunghezza dell'array key2:
$ jq '.key2 | length' file.json
3
Stiamo usando una pipe per passare l'output dell'espressione .key2 come input a length, che come prevedibile produce il numero di elementi nell'array.
Questo è un aspetto di jq a cui bisogna abituarsi:
la maggior parte delle funzioni (ma non tutte) si comporta come i filtri, dove passi i dati all'input del filtro, non come argomento.
In questo esempio, i dati JSON di input vengono ignorati e non hanno alcun impatto sull'output:
$ echo '{"answer": 42}' | jq '6 * 9'
54
Un filtro può produrre più di un valore.
Per esempio, il filtro .[] produce ogni elemento di un array come valore separato:
$ jq -n -c '[1, 2, 3]'
[1,2,3]
$ jq -n -c '[1, 2, 3] | .[]'
1
2
3
Collegare con una pipe un simile filtro a un altro eseguirà il secondo filtro per ogni valore:
$ jq -n -c '[1, 2, 3] | .[] | . * 2'
2
4
6
È come un'iterazione implicita.
Una volta compresa questa tecnica, capirai che i filtri jq più potenti possono essere molto concisi.
Le parentesi si usano per raggruppare sottoespressioni e imporre l'ordine delle operazioni, proprio come in altri linguaggi.
In jq, la necessità di usarle può sembrare un po' sorprendente.
Per esempio, supponiamo di voler costruire un array con 2 elementi: la radice quadrata di 9 e e elevato alla potenza 1.
Le due espressioni singole sono 9 | sqrt e 1 | exp.
Ci aspettiamo che l'output sia l'array [3, 2.7...]
$ jq -n '[ 9|sqrt, 1|exp ]'
[
20.085536923187668,
2.718281828459045
]
Perché non abbiamo ottenuto ciò che ci aspettavamo? jq interpreta il tutto così:
[ ((9|sqrt), 1) | exp ]
jq costruisce un flusso di due elementi (3 e 1) che vengono dati entrambi a exp.
Dobbiamo assicurarci che exp riceva un solo numero come input.
In altre parole, dobbiamo imporre che la pipe venga valutata prima della virgola.
$ jq -n '[ 9|sqrt, (1|exp) ]'
[
3,
2.718281828459045
]
Dal manuale
jq supporta lo stesso insieme di tipi di dato di JSON: numeri, stringhe, booleani, array, oggetti (che nel gergo JSON sono hash con sole chiavi di tipo stringa) e "null".
Imparerai di più su questi argomenti negli esercizi successivi.
Gli spazi bianchi non sono significativi in jq.
Usa spazi, tabulazioni e a capo come preferisci per formattare il tuo codice.
Non siamo a conoscenza di guide di stile per jq.
I valori in jq sono immutabili.
I filtri che modificano un valore producono un nuovo valore.
Questo implica che jq non ha variabili globali:
dovrai abituarti a passare lo stato da un filtro all'altro.
I valori false e null sono considerati falsi. Qualsiasi altro valore
(compreso il numero zero e la stringa, l'array o l'oggetto vuoti) è vero.
Senza entrare troppo nei dettagli (le funzioni saranno l'argomento di un altro esercizio), ecco alcune utili funzioni integrate:
Dato un array come input, produce il numero di elementi dell'array.
$ jq -n '[10, 20, 30, 40] | length'
4
Questo operatore fa cose diverse a seconda del tipo dei suoi operandi: somma i numeri, concatena le stringhe, accoda gli array, unisce gli oggetti.
$ jq -c -n '
3 + 4,
"foo" + "bar",
["a", "b"] + ["c"],
{"m": 10} + {"n": 20}
'
7
"foobar"
["a","b","c"]
{"m":10,"n":20}
add è una funzione che prende un array e restituisce un elemento con tutti gli elementi sommati insieme secondo le regole di +.
[1, 2, 3] | add produce 6.
Dato un array come input e un filtro come argomento, produce un array in cui il filtro è applicato a ogni elemento
$ jq -c -n '[10, 20, 30, 40] | map(. / 5)'
[2,4,6,8]
Dato un certo input e un filtro come argomento:
null, davvero nessun output)Per esempio, dati alcuni numeri, seleziona quelli divisibili per 3
$ jq -n 'range(10) | select(. % 3 == 0)'
0
3
6
9
Ricorda che range produce un flusso di numeri.
select verrà chiamato una volta per ogni numero.
Vengono prodotti solo i numeri che «passano» l'espressione.
Spesso hai bisogno di selezionare elementi di un array. Ci sono un paio di modi per farlo.
Con l'input ["Anne", "Bob", "Cathy", "Dave"], seleziona i nomi che hanno lunghezza 4.
usa map e select insieme
map(select(length == 4))
scomponi l'array nei suoi elementi, applica select su quel flusso e raccogli i risultati
[ .[] | select(length == 4) ]
I commenti iniziano con il carattere # e proseguono fino alla fine della riga.