jq – command line JSON processor
JSON-Daten gehören heute zum Alltag vieler Entwickler:innen. Mit jq lassen sich diese direkt auf der Kommandozeile filtern, durchsuchen und transformieren. Schnell, elegant und ohne komplizierte Skripte. Dieser Spickzettel zeigt anhand praxisnaher Beispiele, wie du jq effizient für die tägliche Arbeit einsetzen kannst.
Überblick
jq (JSON Query) ist ein Command Line Processor für JSON. Typischerweise bieten Shells
wie z.B. bash oder ähnliche Shells keine Unterstützung an, um direkt mit JSON-Daten
arbeiten zu können. Und die Verwendung von sed oder grep machen dir das Leben in
diesem Fall auch nicht einfacher… jq lässt sich sehr einfach in den üblichen
Betriebssystemen installieren, falls dieses Tool nicht bereits vorhanden ist. Halte dich an
die jeweilige Installationsanleitung, um eine aktuelle Version von jq zu installieren.
jq basiert auf dem Konzept von Filtern, die einen JSON-Datenstrom verarbeiten. Jeder
Filter nimmt einen JSON-Datenstrom entgegen und gibt einen JSON-Datenstrom an die
Standardausgabe aus. Wie du gleich sehen wirst, stehen dir zahlreiche vordefinierte
Filter zur Verfügung. Mithilfe von Pipes kannst du diese Filter miteinander kombinieren,
um schnell komplexe Operationen und Transformationen auf deine JSON-Daten
anzuwenden.
Einführung in jq
Bevor du mit jq arbeiten kannst, musst du entweder jq bei dir lokal installieren oder du
verwendest den JQ Playground, um die nachfolgenden Beispiele auszuprobieren.
Der einfachste Fall ist das ‚.‘-Filter, welcher den Input entgegennimmt und in einem gut
lesbaren Format wieder ausgibt (pretty-print). Du kannst damit gleich die erste Beispiel-
Datei persons.json erstellen, damit die nachfolgenden Filter-Beispiele einfacher und
übersichtlicher bleiben:
[
{"name": "Alice", "role": "Admin", "active": true, "age": 37, "alias": "Ali"},
{"name": "Bob", "role": "User", "active": false, "age": 56},
{"name": "Charlie", "role": "User", "active": true, "age": 21, "alias": "JJ"}
]
Der Zugriff auf einzelne Attribute erfolgt via Keys, welche mit einem Punkt beginnen oder voneinander getrennt werden. Der Zugriff auf Arrays erfolgt in jq mit den eckigen Klammern, wiederum mit einem Punkt beginnend («.[]»), die Iteration über die Array-
Elemente via Pipe-Symbol «|».
jq '.[].name' persons.json
- Der Punkt «.» am Anfang referenziert die ganze JSON-Datei.
- Die nachfolgenden eckigen Klammern definieren einen Array bzw. eine Liste von
JSON-Objekten. - «.name» extrahiert in jedem JSON-Objekt das Attribut bzw. JSON-Objekt mit dem
Schlüssel «name» (key value pair).
Es stehen dir eine Vielzahl von Funktionen zur Verfügung, um die JSON-Daten zu verarbeiten, z.B. length, min, max (und die meisten Math-Funktionen der C-Math-Library), Vergleichs- und Filterfunktionen, Regular Expressions oder Unique Values. Dazu wiederum ein paar Beispiele mit obigen Daten:
jq '[.[] | {name: .name, alias: .alias}]' persons.json
- Es wird wiederum das ganze JSON-Objekt / der gesamte Array verarbeitet.
- Es werden die beiden Attribute «name» und «alias» extrahiert und ausgegeben.
- Da Bob kein Attribut «alias» definiert hat, wird hier «null» ausgegeben.
jq 'map({name: .name, alias: .alias} | del(.[] | select(. == null)))' persons.json
- Dasselbe Beispiel wie oben, diesmal werden Null Values entfernt.
- Du siehst, dass nun bei Bob nur noch das Attribut «name» aufgeführt wird, weil Bob kein Attribut «alias» definiert hat.
jq '.[] | select(.active == true) | .name' persons.json
- Es wird der gesamte Array verarbeitet.
- Allerdings werden nur JSON-Objekte weitergegeben, welche das Attribut «active» auf «true» gesetzt haben.
- Von diesen gefilterten JSON-Objekten wird dann der Name ausgegeben.
jq '[.[].age] | min' persons.json jq 'map(.age) | min' persons.json
- Beide Varianten verarbeiten den ganzen Array und führen zum Ziel.
- In jedem JSON-Objekt wird das Attribut «age» extrahiert.
- Aus der Liste aller Altersangaben wird der kleinste Wert bestimmt (21).
jq '[.[].age] | add / length' persons.json
- Wiederum wird das Alter jeder Person extrahiert.
- Aus der Liste aller Altersangaben wird der Durchschnitt bestimmt (38)
jq Advanced
Häufig stehst du vor der Aufgabe, bestimmte Daten aus der JSON-Struktur herauszufiltern und in ein neues Format umzuwandeln. In einem zweiten, etwas komplexeren Beispiel verwenden wir die Daten, wie sie von aareguru bezogen werden können. Mit dem curl-Befehl können die Daten direkt in die lokale Datei aareguru.json geschrieben werden:
curl -s "https://aareguru.existenz.ch/v2018/widget?app=my.app.ch&version=1.0.42" | jq -r > aareguru.json jq '.values' aareguru.json jq '.cities' aareguru.json
- Die Beispiel-Datei aareguru.json enthält zwei Abschnitte: einerseits die Messwerte
«values», andererseits die Ortschaften «cities», welche per Key von den Messstellen referenziert werden.
jq '.values[].temperature' aareguru.json
- Es werden alle gemessenen Temperaturen ausgegeben
Es fragt sich nur, an welchem Ort welche Temperatur gemessen worden ist. Zudem wollen wir die Aufgabe erweitern und dazu kannst du bei einem der Orte die gemessene Temperatur löschen, d.h. sowohl den Key «temperature» als auch den Wert entfernen (inkl. nachfolgendem Komma). Wenn du nun obiges Beispiel nochmals ausführst, siehst du in der Zahlenreihe ein «null».
jq -r '.values as $val | .cities[] | [.name, ($val[.key].temperature // "n/a")] | @csv' aareguru.json
- Die Values (Messwerte) werden als Variable $val referenziert.
- Danach werden alle Orte verarbeitet, indem der Name des Ortes sowie die Temperatur via Key «.key» aus den Messwerten ausgelesen wird.
- Falls einer der Temperaturen nicht vorhanden ist, wird der Optional Operator «//»
verwendet und der Wert «n/a» zurückgegeben. - Schliesslich wird der Output im CSV-Format ausgegeben. Wichtig dabei ist, dass jq mit der Option «-r» aufgerufen wird, damit der Output im Raw-Format erfolgt.
- Du erhältst eine Liste von Ortschaften mit den dazugehörigen Temperaturen.
- Dort, wo du die Temperatur gelöscht hast in der Beispiel-Datei, siehst du den Eintrag «n/a».
Alternativ dazu kannst du weitere Features von jq nutzen, um diese (und noch komplexere) Aufgabe(n) einfach und übersichtlich zu lösen:
jq -r '.values as $val | .cities[] |
# define values and check for null values
(.name) as $name |
($val[.key].temperature) as $temp |
if ($name == null or $temp == null) then
debug("missing data: name=\($name // "NULL"), temp=\($temp // "NULL")") | empty
else
[$name, $temp] | @csv
end' aareguru.json
- Für die notwendigen Output-Attribute «name» und «temperature» werden zwei Variablen $name und $temp definiert.
- Falls eine der beiden Variablen nicht definiert ist, wird ein Fehlertext als Debug
Output generiert. Dieser erscheint standard-mässig auf stderr. - Ansonsten werden die beiden Variablen im CSV-Format ausgegeben.
Weiterführende, interessante Themen:
• Alternative Operator: //
• Error Suppression / Optional Operator: ?
• I/O inkl. debug-Filter
• Streaming zur Verarbeitung von sehr grossen JSON-Files (> 1GB)
Fazit oder warum jq?
jq wird oft als «sed für JSON-Daten» bezeichnet – ein Schweizer Taschenmesser für die
Kommandozeile zum Aufteilen und Transformieren strukturierter Daten. Wir setzen jq in unseren Projekten z.B. ein, um Datenexporte miteinander zu vergleichen. Die Datenexporte stammen von verschiedenen (Um-)Systemen und wir haben keinen grossen Einfluss auf deren Inhalt und Struktur. jq hilft uns dabei enorm, die JSON-Daten auf einen gemeinsamen Nenner zu bringen und danach Vergleiche durchzuführen.
Mit den vielen Filter- und Transformationsmöglichkeiten konnten wir alle bisherigen Aufgaben effizient lösen. Und immer wieder faszinierend ist die enorme Performance, die jq an den Tag legt: selbst sehr grosse Dateien (> 1GB) verarbeitet jq in Sekundenschnelle!