Als Softwareentwickler im Webbereich steht man oft vor der Herausforderung aus einer Menge JSON sinnvolle Werte herauslesen zu müssen. Entweder weil ein Backend-System JSON zur Datenübertragung benutzt oder weil JSON als Dateiformat für Konfigurationsdateien verwendet wird.

Solange eine JSON-Struktur klein genug ist, kann man Werte darin mit bloßem Auge erkennen und notfalls auch per Hand in eine lesbare Struktur einrücken. Ist die Datenmenge allerdings größer als ein paar Bildschirmseiten, steht man von einer Menge Zeichensalat dem man nur schwer Informationen entlocken kann.

Um dies zu demonstrieren, kann man zum Beispiel Informationen über die Postleitzahlen einzelner Länder über die kostenlose API des Projekts GeoNames abrufen.

Dazu kann man zum Beispiel curl wie folgt verwenden

  $ curl -v http://api.geonames.org/postalCodeCountryInfoJSON?username=demo&style=full

was eine ziemliche Textwüste zurück gibt. Diese Menge von Text ließe sich zwar mittels des Parameters formatted=true in der URL lesbar gestalten, jedoch bleibt dadurch immer noch eine mehrere Bildschirmseiten umspannende, schwer durchsuchbare Struktur zurück. Genau hier setzt jq an.

Ausgabe von curl

Installation

Das schöne an jq ist, dass es in C ohne Abhängigkeiten geschrieben wurde und für Linux, OSX, FreeBSD, Solaris und Windows zur Verfügung steht. Unter Ubuntu oder Debian kann man es via sudo apt-get install jq installieren. Benutzer von OSX nutzen zum Beispiel homebrew und installieren via brew install jq. Alternativ kann man sich das Programm auch direkt von der Homepage des Projekts laden und zum Beispiel mit scp auf einen Server schieben.

Strukturierung der Textwüste

Um auf das Beispiel mit den Postleitzahlen zurückzukommen werden wir mit jq nach den Informationen über polnische Postleitzahlen suchen. Wird jq ohne Filter verwendet wird es die JSON-Struktur nur einrücken und ggf. Syntaxhighlighting durchführen.

  $ curl http://api.geonames.org/postalCodeCountryInfoJSON\?username\=demo\&style\=full | jq
    {
       "geonames": [
           {
             "numPostalCodes": 7,
             "maxPostalCode": "AD700",
             "countryCode": "AD",
             "minPostalCode": "AD100",
             "countryName": "Andorra"
           },
           {
             "numPostalCodes": 20260,
             "maxPostalCode": "9431",
             "countryCode": "AR",
             "minPostalCode": "1601",
             "countryName": "Argentina"
           },
    ...

Um nach einzelnen Ländern filtern zu können, muss man zuerst die Struktur des zurückgelieferten geonames Arrays auflösen um nur noch die einzelnen Elemente für die weitere Filterung zu erhalten. Dazu besitzt jq den Filter .[] der dazu dient, die Elemente eines Objekts oder Arrays aufzulisten und somit die Struktur zu verflachen.

Im obigen Beispiel verwenden wir .[] zwei mal hintereinander um zu erst das Objekt, dessen einzelner Key geonames ist, aufzulösen und danach die Inhalte vom Array einzeln aufzulisten. Dazu werden die beiden Filter innerhalb der Filteranweisung mittels Pipe-Symbol verkettet: jq '.[] | .[]'

  $ curl -v http://api.geonames.org/postalCodeCountryInfoJSON\?username\=demo\&style\=full \
   | jq '.[] | .[]'
    {
      "numPostalCodes": 7,
      "maxPostalCode": "AD700",
      "countryCode": "AD",
      "minPostalCode": "AD100",
      "countryName": "Andorra"
    }
    {
      "numPostalCodes": 20260,
      "maxPostalCode": "9431",
      "countryCode": "AR",
      "minPostalCode": "1601",
      "countryName": "Argentina"
    }
    ...

Um die Werte für Polen zu finden, reicht es nun den vorhandenen Filtern ein weiteren Filter hinzuzufügen. Mittels .select() kann man mittels boolschen Ausrücken suchen.

  $ curl -v http://api.geonames.org/postalCodeCountryInfoJSON\?username\=demo\&style\=full \
   | jq '.[] | .[] | select(.countryCode == "PL")'
    {
      "numPostalCodes": 21980,
      "maxPostalCode": "99-440",
      "countryCode": "PL",
      "minPostalCode": "00-001",
      "countryName": "Poland"
    }

Fazit

Ich nutze jq gerne um die JSON-Inhalte zu analysieren und zu extrahieren, was mir bei der Fehlersuche oder während der Entwicklung schon oft geholfen hat. Neben dem umfangreichen Manual gibt es mit jqPlay eine tolle Möglichkeit jq ohne Installation auszuprobieren. Auch das Tutorial ist einen Blick wert. Neben den von mir vorgestellten Filtermöglichkeiten gibt es noch deutlich mehr Möglichkeiten. So kann man mit Regular Expressions filtern, mit Bedingungs- und Vergleichsoperatoren arbeiten, Berechnungen durchführen und vieles mehr damit machen.