Unter Linux gibt es zahlreiche Werkzeuge und Bibliotheken, die Sie beim Weiterverarbeiten von JSON-Daten unterstützen.
Im vorhergehenden Artikel [1] in der letzten Ausgabe widmeten wir uns dem Datenformat JSON und insbesondere dessen Erstellung. Es ging also darum, welche Linux-Werkzeuge Ihnen ihre Ausgabe im JSON-Format liefern und wie Sie diese Daten auf der Shell ausgeben. Hier geht es nun darum, wie Sie JSON in anderen Programmen als Eingabe weiterverarbeiten (post-processing). Daher stehen verschiedene Werkzeuge und Bibliotheken im Fokus, die Sie dazu in die Lage versetzen.
Lesen und schreiben
Das Kürzel JSON steht für Javascript Object Notation, ein kompaktes Dateiformat, das zum Austausch strukturierter Daten große Verbreitung gefunden hat. Insbesondere bei Webanwendungen und mobilen Apps kommt es in Verbindung mit Javascript zum Übertragen von Daten zwischen Webserver und Webbrowser häufig zum Einsatz. Mittlerweile trifft man es auch bei anderen Anwendungen immer häufiger an. Bei REST-Services bildet JSON derzeit den De-facto-Standard.
Auch wenn sich das Format an Javascript orientiert, so existieren doch Parser in nahezu allen Programmiersprachen. Dazu zählen neben Awk und C/C++ auch Fortran, Go, Lisp, Lua, Python und Visual Basic.
Bei genauerer Betrachtung einer JSON-Datenstruktur sehen Sie, dass es sich um ein relativ einfach zu lesendes Textformat handelt. Klammern, Doppelpunkte und Kommas separieren die einzelnen Elemente; die Daten lassen sich beliebig verschachteln. Das erlaubt beispielsweise die Abbildung von Listen, Arrays oder Objekten. Die Tabelle “Datentypen von JSON” stellt die elementaren Datentypen zusammen, die JSON kennt.
|
Datentyp |
Beschreibung |
|---|---|
|
Zeichenketten |
Alle Unicode-Zeichen mit Ausnahme von |
|
Zahlen |
Zahlenwerte inklusive Hexadezimal- und Exponentialwerten, zum Beispiel |
|
Boolesche Werte |
Wahrheitswerte |
|
Arrays |
Durch Kommas geteilte, ungeordnete Listen von Eigenschaften, wobei auch Objekte ohne Eigenschaften zulässig sind. |
|
Objekte mit Eigenschaften |
Notation als Schlüssel-Wert-Paare |
|
Nullwerte |
|
JSON unterstützt dabei jedoch nicht alle Datentypen von Javascript [2]. Die drei Datentypen NaN (Not a Number), +Infinity (Zahlenwerte größer als 1.797693134862315E+308) sowie -Infinity (Zahlenwerte kleiner als -1.797693134862316E+308) reduziert es schlicht zum Wert null.
Korrektheit prüfen
Treffen JSON-Daten über eine Schnittstelle bei Ihnen ein, ist es gute Programmierpraxis, die empfangenen Daten vor dem weiteren Verarbeiten auf deren Korrektheit hin zu prüfen. Das vermeidet Probleme bei der späteren Verwendung. Die Überprüfung umfasst zwei Stufen:
- Syntaktische Korrektheit: Ist die Schreibweise korrekt, passen also alle Klammern (gleiche Anzahl öffnender und schließender Klammern), Kommas und Anführungszeichen?
- Korrektheit der Datenfelder: Stimmt die empfangene Datenstruktur mit deren Definition (JSON-Schema) überein?
Für die erste Stufe nutzen Sie am besten Jsonlint [3], das Sie bereits im Teil 1 dieser Artikelserie kennengelernt haben. Für die zweite Stufe benötigen Sie ein entsprechendes JSON-Schema, sprich: eine Beschreibung der Datenstruktur. Diese Beschreibung vergleichen Sie mit den empfangenen Daten.
Bei JSON-Schema.org finden Sie eine Übersicht zu den Validatoren [4], sortiert nach den verschiedenen Programmiersprachen, in denen sie entwickelt wurden. Daraus verwenden wir hier beispielhaft das in PHP implementierte Werkzeug Validate-json [5]. Stehen Sie eher auf Python, erfüllt Jsonschema [6] denselben Zweck. Der Aufruf beider Werkzeuge ist identisch.
JSON-Schema festlegen
Listing 1 zeigt das JSON-Schema, mit denen Sie das exakte Format Ihrer Datenstruktur festlegen. Es passt zum Buchbestand, den Sie bereits aus dem vorhergehenden Teil dieser Artikelserie kennen. Das Schema wurde hier in der Datei buchbestand-schema.json im lokalen Verzeichnis abgelegt.
Listing 1
JSON-Schema
{
"$schema": "http://json-schema.org/draft/2019-09/schema",
"title": "Book",
"type": "object",
"required": ["author", "title", "publication"],
"properties": {
"author": {
"type": "string",
"description": "The author's name"
},
"title": {
"type": "string",
"description": "The book's title"
},
"publication": {
"type": "number",
"minimum": 0
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
Die Schemadefinition referenziert in der zweiten Zeile den verwendeten JSON-Standard (hier den Entwurf aus dem September 2019). Dieser enthält eine Reihe von Schlüsselworten. Die Tabelle “JSON-Schlüsselworte” erklärt diese näher; eine vollständige Liste aller zulässigen Schlüsselworte finden Sie auf JSON-Schema.org [7].
|
Schlüsselwort |
Beschreibung |
|---|---|
|
|
Beschreibung der Schema-Spezifikation |
|
|
Titel des Schemas |
|
|
Typ der JSON-Daten |
|
|
Eigenschaften der einzelnen Werte (Schlüssel und für das Feld zulässige Werte) |
|
|
Liste der erforderlichen Eigenschaften |
|
|
Datentyp eines Eintrags |
|
|
Minimalwert eines Eintrags |
|
|
Maximalwert eines Eintrags |
|
|
Minimale Zeichenanzahl für einen Eintrag |
|
|
Maximale Zeichenanzahl für einen Eintrag |
|
|
Regulärer Ausdruck für einen Vergleich mit dem Wert eines Eintrags |
Nun geht es daran, Datensätze zu validieren, also zu überprüfen, ob sie mit dem festgelegten Schema übereinstimmen. Listing 2 zeigt einen einzelnen Datensatz aus dem Buchbestand in lesbarer Form. Die kompakte Variante des Datensatzes beinhaltet alle Klammern und Felder in einer einzigen Zeile. Für den nachfolgenden Test speichern wir den Datensatz in der lokalen Datei datensatz.json.
Listing 2
JSON-Datensatz
{
"author": "Stephen Fry",
"title": "The Hippopotamus",
"publication": 1994
}
Das Werkzeug Validate-json erwartet im Aufruf zwei Parameter, den Datensatz und das Schema (Listing 3). Geht alles gut, bleibt die Ausgabe ohne weitere Rückmeldung (Zeile 2), andernfalls meckert Validate-json herum (Zeilen 4 und 5). Um die Fehlermeldung ab Zeile 4 zu provozieren, haben wir aus der numerischen Angabe für das Jahr der Veröffentlichung (1994) einen String "1994" gemacht. Damit stimmt der Datentyp im Datensatz nicht mehr mit dem hinterlegten Datentyp im JSON-Schema überein. Validate-json hat also allen Grund, sich bei uns darüber zu beschweren – wir wollten das ja so haben.
Listing 3
validate-json aufrufen
$ validate-json datensatz.json buchbestand-schema.json $ $ validate-json datensatz.json buchbestand-schema.json JSON does not validate. Violations: [publication] String value found, but a number is required
Daneben bieten Ihnen auch Programmiersprachen passende Helferbibliotheken an. In Python ist es beispielsweise das zuvor genannte Jsonschema, für NodeJS das Express [8] Framework [9].
JSON verarbeiten
Die Liste der Kommandozeilenwerkzeuge und Helfer, die Daten im JSON-Format einlesen, in andere Formate umwandeln, darin suchen oder sie modifizieren, fällt recht umfangreich aus. Wir haben nach mehr als 20 Einträgen aufgehört zu zählen (siehe Tabelle “Kommandozeilenwerkzeuge”). Der Entwickler Ilya Sher pflegt dazu eine passende, kommentierte Übersicht [10].
|
Werkzeug |
Einsatzbereich (Auswahl) |
|---|---|
|
Formate von und nach JSON umwandeln (BSON, Bencode, JSON, TOML, XML, YAML, …) |
|
|
Filtern von JSON-Daten |
|
|
Jello [24] |
JSON-Daten mit Python-Syntax filtern |
|
Jtbl [25] |
Ausgabe in einer Tabelle |
|
Underscore [26] |
Verarbeiten über die Kommandozeile |
Aus dieser Liste greifen wir Jtbl heraus. Das Werkzeug nimmt JSON-Datensätze entgegen und häkelt daraus eine hübsche Tabelle. In Abbildung 1 sehen Sie das für den Buchbestand. Jeder Datensatz ist dabei in einer separaten Zeile dargestellt. Dabei fällt auf, dass Jtbl nur mit flachen JSON-Strukturen zurechtkommt. Bei Verschachtelungen muss es bislang passen.
Elementweiser Zugriff
Im vorigen Teil dieser Artikelserie benutzten Sie Jq, um einzelne Elemente mithilfe von Ausdrücken aus dem JSON-Datenstrom herauszufischen. Bei stark verschachtelten Datenstrukturen ist das umständlich und gelingt mithilfe einer Pfadangabe wesentlich einfacher. Darüber erreichen Sie ausgewählte Knoten und Attribute sauber. Analog zu XPath für XML existieren Jmespath [11] (ausgesprochen “James Path”) und Jsonpath [12]. Ersteres steht in Python, PHP, Javascript oder auch Lua bereit, Letzteres in Javascript, PHP sowie in Java [13].
Passend zur Inventarliste des Buchbestands werden damit komplexere Ausdrücke möglich. In der Tabelle “Ausdrücke in Jmespath” sehen Sie eine Auswahl. Die Ausdrücke lesen Sie von links nach rechts und benennen Knoten- oder Attributnamen in der Reihenfolge, in der Sie sich an der Datenstruktur entlanghangeln möchten. Zwei Ebenen von Knoten beziehungsweise Attributen trennen Sie dabei mit einem Punkt voneinander. Mengen und Muster geben Sie in eckigen Klammern an, beispielsweise book[*] für alle Knoten der Liste book. Die Angabe book[?author == `Meier`] übernimmt alle Knoten aus dem Datensatz, bei denen das Attribut author den Wert Meier besitzt.
|
Ausdruck |
Bedeutung |
|---|---|
|
|
alle Buchtitel |
|
|
alle Buchtitel des Autors Ken Follett |
|
|
alle nach 1990 veröffentlichten Bücher |
Bitte beachten Sie bei der Formulierung der Ausdrücke die richtigen Anführungszeichen (“Quotierung”). Werte zum Vergleich schließen Sie im Aufruf in Backticks ein (`), unabhängig davon, ob es sich um Zeichenketten oder Zahlenwerte handelt.
In Listing 4 sehen Sie die drei Ausdrücke aus der Tabelle “Ausdrücke in Jmespath” in Aktion in einem Python-Skript. Verwendet haben wir hier die JSON-Implementierung von Jmespath. Während die Bibliothek Json [14] fester Bestandteil von Python ist, gehört Jmespath zu den Extras, die Sie vor der Verwendung entweder über Pip oder die Paketverwaltung Ihrer Linux-Distribution nachinstallieren. Das dazugehörige Paket für Debian GNU/Linux und Ubuntu heißt python3-jmespath.
Listing 4
find-json-path.py
import jmespath
import json
expression1 = jmespath.compile('book[*].title')
expression2 = jmespath.compile('book[?author == `Ken Follett`].title')
expression3 = jmespath.compile('book[?publication > `1990`]')
with open("inventar-buchbestand.json") as jsonFile:
jsonData = json.load(jsonFile)
# book titles
print("Buchtitel:")
bookTitles = expression1.search(jsonData)
for title in bookTitles:
print(title)
print(" ")
# all the books by Ken Follett
print("Alle Bücher von Ken Follett:")
bookTitles = expression2.search(jsonData)
for title in bookTitles:
print(title)
print(" ")
# all the books published later than 1990
print("Alle Bücher, die nach 1990 veröffentlicht wurden:")
books = expression3.search(jsonData)
for item in books:
author = item["author"]
title = item["title"]
publication = item["publication"]
print("Autor : %s" % author)
print("Titel : %s" % title)
print("Veröffentlichung: %i" % publication)
print(" ")
Nachdem Sie im Skript zunächst die beiden Python-Bibliotheken Json und Jmespath geladen haben (Zeile 1 und 2), definieren Sie drei Ausdrücke oder Suchmuster als Objekte mit den Namen expression1, expression2 und expression3. Ist Ihnen die Python-Bibliothek Re [15] für reguläre Ausdrücke vertraut, kennen Sie das Vorgehen von dort bereits.
In den Zeilen 8 und 9 lesen Sie den Buchbestand als JSON-Datei ein und laden den Inhalt der Datei mithilfe der Funktion load() als Dictionary in die Variable jsonData. Die Suche über den Buchbestand erfolgt mithilfe der Methode search() aus dem Suchmuster-Objekt. Der Aufruf expression2.search(jsonData) sucht beispielsweise alle Buchtitel heraus, die zum Autor Ken Follett gehören.
Als Rückgabe liefert search() eine Liste der Suchtreffer, die Sie in einer For-Schleife nacheinander ausgeben. Abbildung 2 zeigt die Ausgabe der Suchtreffer für alle drei zuvor definierten Suchpfade.
JSON-Bibliotheken
Bevorzugen Sie statt Python eine andere Programmiersprache, stehen Sie nicht ohne Anschluss an JSON da. Die Tabelle “Auswahl von JSON-Bibliotheken” zeigt eine Auswahl entsprechender Bibliotheken und Module. Stehen Ihnen unterschiedliche Implementierungen für eine Programmiersprache zur Verfügung, ist eine Empfehlung ohne Kenntnis der Menge und Struktur der zu verarbeitenden JSON-Daten schwierig. Nach einem Benchmark-Test sind Sie schlauer [16], was in Ihrem Fall am besten passt.
|
Sprache |
Bibliotheken (Auswahl) |
|---|---|
|
C++ |
RapidJSON, Jansson |
|
Go |
Encoding/json |
|
LISP |
CL-JSON |
|
Lua |
Json.lua |
|
NodeJS |
Express |
|
Perl |
JSON::Parse, JSON::PP, JSON::XS |
|
PHP |
Json |
|
Python |
Simplejson, Hyperjson, Json, Jsonschema, Orjson, Rapidjson, Ultrajson, Pandas |
|
Ruby |
Json |
|
Tcl |
Json |
Listing 5 zeigt, wie Sie in der Programmiersprache Go auf JSON-Objekte zugreifen. Nach dem Import der beiden Module Encoding/json und Fmt legen Sie eine Datenstruktur Book an, die die drei Variablen Author, Title und Publication umfasst. Auf diese Datenstruktur greifen Sie in der Hauptfunktion main() zurück, indem Sie darin eine Variable book mit diesem Typ deklarieren.
Listing 5
extract-json.go
package main
import (
"encoding/json"
"fmt"
)
type Book struct {
Author string
Title string
Publication string
}
func main() {
bookJson := `{"author": "Stephen Fry", "title": "The Hippopotamus", "publication": "1994"}`
var book Book
json.Unmarshal([]byte(bookJson), &book)
fmt.Println("Autor: ", book.Author)
fmt.Println("Titel: ", book.Title)
fmt.Println("Veröffentlichung: ", book.Publication)
}
Die Variable bookJson erfasst den Datensatz für ein Buch. Mithilfe der Methode Unmarshal() aus dem Modul Json entpacken Sie den Datensatz byteweise und weisen den Inhalt den Komponenten aus book zu. Danach geben Sie mithilfe der Methode Println() die Inhalte der Komponenten aus. Weitere Informationen zur Verarbeitung entnehmen Sie dem lesenswerten Blog-Beitrag von Soham Kamani [17].
Speichern Sie Listing 5 in der Datei extract-json.go ab und führen den Code aus, dann erhalten Sie eine Ausgabe wie in Listing 6.
Listing 6
Ausgabe
$ go run extract-json.go
Autor: Stephen Fry
Titel: The Hippopotamus
Veröffentlichung: 1994
Ausblick
Der Einsatz von JSON bietet sich an, wenn die Daten den unterstützten Formaten entsprechen, Zeichenketten nicht beliebig lang ausfallen und Sie nur den Datenaustausch realisieren müssen, die Dokumentation der Daten jedoch an anderer Stelle verwaltet wird. Eine weitere Stärke von JSON ist, dass es sich in sehr vielen Sprachen verarbeiten lässt.
Bei großen Datenmengen kann sich allerdings die Dateigröße nachteilig auf die Verarbeitungsgeschwindigkeit auswirken. Hier könnte dann zum Beispiel Protobuf [18] eine Alternative bieten. Weitere Informationen zu Serialisierungsformaten und dem praktischen Umgang mit JSON entnehmen Sie den Beispielen aus dem Jupyter Tutorial [19]. (jlu)
Danksagung
Die Autoren bedanken sich herzlich bei Gerold Rupprecht für seine Kritik und Anregungen bei der Erstellung des Artikels.
Über die Autoren
Veit Schiele ist Gründer und Geschäftsführer der Cusy GmbH, die datenschutzkonforme Werkzeuge für die Software-Entwicklung und eine Plattform für Forschungs-Software und -daten bereitstellt. Er ist Autor der Jupyter- und PyViz-Tutorials. Frank Hofmann arbeitet zumeist von unterwegs aus als Entwickler, Trainer und Autor. Bevorzugte Arbeitsorte sind Berlin, Genf und Kapstadt. Er gehört zu den Verfassern des Debian-Paketmanagement-Buchs.
Infos
-
JSON (Teil 1): Frank Hofmann, “Datenkellner”, LU 02/2021, S. 80, https://www.linux-community.de/45554
-
“Javascript Data Types”: https://javascript.info/types
-
Jsonlint: https://jsonlint.com
-
JSON-Validatoren: https://json-schema.org/implementations.html#validators
-
Validate-json: https://github.com/justinrainbow/json-schema
-
Jsonschema: https://github.com/Julian/jsonschema
-
Validierung von JSON-Daten: http://json-schema.org/draft/2019-09/json-schema-validation.html
-
NodeJS Express: https://expressjs.com
-
“Read and parse POST/PATCH/PUT request JSON or form body with Express and no dependencies”: https://codewithhugo.com/parse-express-json-form-body/
-
“List of JSON tools for command line”: https://ilya-sher.org/2018/04/10/list-of-json-tools-for-command-line/
-
Jmespath: https://jmespath.org
-
Jayway Jsonpath: https://github.com/json-path/JsonPath
-
Python-JSON-Bibliothek: https://docs.python.org/3/library/json.html
-
Python-Re-Bibliothek: https://docs.python.org/3/library/re.html
-
“Choosing a faster JSON library for Python”: https://pythonspeed.com/articles/faster-json-library/
-
“How to Parse JSON in Golang”: https://www.sohamkamani.com/blog/2017/10/18/parsing-json-in-golang/
-
Serialisierungsformate/JSON (Jupyter Tutorial): https://jupyter-tutorial.readthedocs.io/de/latest/data/serialisation-formats/json.html
-
Underscore: https://github.com/ddopson/underscore-cli







