JSON ist derzeit eines der wich­tigs­ten Formate für den Da­ten­aus­tausch zwischen An­wen­dun­gen, vor allem über das Internet. JSONPath ist eine Aus­drucks­spra­che, mit der man spe­zi­fi­sche Daten aus JSON-Objekten lesen kann. Wir stellen Ihnen die Python-Im­ple­men­tie­rung von JSONPath vor und erklären die wich­tigs­ten An­wen­dun­gen anhand von leicht ver­ständ­li­chen Bei­spie­len.

Was ist Python JSONPath?

JSON ist ein sys­tem­über­grei­fen­des Da­tei­en­for­mat, mit dem man den Austausch von struk­tu­rier­ten Daten zwischen An­wen­dun­gen ver­ein­fa­chen und op­ti­mie­ren kann. JSON-Dateien bestehen aus auf­ge­lis­te­ten Schlüssel-Wert Paaren (Englisch: Key-Value Pairs). Die Werte können ver­schie­de­ne Da­ten­ty­pen annehmen, sowohl primitive Werte als auch Objekte. Objekte können wiederum ihre eigene Schlüssel-Wert Paare enthalten. Da JSON von fast allen modernen Systemen ver­stan­den wird, kann es für den Da­ten­aus­tausch zwischen jeder Art von Anwendung verwendet werden, sowohl lokal auf dem Rechner als auch über das Internet.

Jedoch braucht nicht jede Anwendung immer alle Daten, die in einer JSON-Datei erfasst sind. In solchen Fällen bietet sich JSONPath an. JSONPath ist eine Aus­drucks­spra­che mit der man gezielt spe­zi­fi­sche In­for­ma­tio­nen aus JSON-Objekten lesen kann. In den meisten Pro­gram­mier­spra­chen muss JSONPath aus einer externen Bi­blio­thek im­por­tiert werden. Da diese Bi­blio­the­ken für jede Sprache separat im­ple­men­tiert werden müssen, können die un­ter­schied­li­chen Bi­blio­the­ken bzw. Im­ple­men­tie­run­gen leicht von­ein­an­der abweichen.

Das Python jsonpath-ng Modul

jsonpath-ng ist die wohl gängigste Python Im­ple­men­tie­rung von JSONPath. Es gibt auch andere JSONPath-Im­ple­men­tie­run­gen für Python, wie zum Beispiel jsonpath und jsonpath-rw. Diese sind al­ler­dings weniger beliebt und umfassend, also kon­zen­trie­ren wir uns hier aus­schließ­lich auf jsonpath-ng.

In­stal­la­ti­on

jsonpath-ng können Sie sehr leicht über Ihre Shell in­stal­lie­ren. Dafür müssen Sie nur das Kommando pip install jsonpath-ng eingeben.

Hinweis

Die In­stal­la­ti­on erfolgt über den Pa­ket­ma­na­ger pip, der für Python stan­dard­mä­ßig verwendet wird. Falls Sie diesen Pa­ket­ma­na­ger nicht auf Ihrem System in­stal­liert haben, müssen Sie dies zunächst tun. Weitere In­for­ma­tio­nen dazu finden Sie auf der Webseite von Pip.

Syntax

Mit JSONPath kann man komplexe Abfragen auf JSON-Objekten ausführen. Um dies zu er­mög­li­chen, gibt es im Modul mehrere Methoden, Ope­ra­to­ren, und atomare Ausdrücke, mit denen spe­zi­fi­sche Daten se­lek­tiert und abgefragt werden können. Die zwei wich­tigs­ten JSONPath Methoden sind parse() und find(). Mit parse() kann man Abfragen de­fi­nie­ren, die dann beliebig oft re­fe­ren­ziert und wie­der­holt werden können. Mit find() kann man diese Abfragen auf JSON-Daten ausführen, um konkrete Werte zu ex­tra­hie­ren. Das folgende Beispiel dient zur Ver­an­schau­li­chung.

import json
import jsonpath_ng as jp
raw_data = '''
{
    "name": "Johannes",
    "alter": 30,
    "wohnort": "Bielefeld"
}
'''
json_object = json.loads(raw_data)
name_query = jp.parse("$.name")
result = name_query.find(json_object)
print(result[0].value) # Ausgabe: Johannes
Python

Im Beispiel oben wurden JSON-Daten in Form eines Strings mittels json.loads in ein Dic­tion­a­ry-Objekt um­ge­wan­delt. Dies ist das Format, mit dem Python am besten arbeiten kann. Beim Anlegen von name_query wurde die Anfrage "$.name" definiert, die den Wert von “name” zu­rück­ge­ben soll. Diese wurde dann an­schlie­ßend mit find() auf dem JSON-Objekt angewandt. Das Ergebnis der Anfrage wurde in der Variable result ge­spei­chert und mit result[0].value aus­ge­le­sen.

Hinweis

Damit Python JSON-Daten aus einem String oder aus einer JSON-Datei lesen kann, muss zu­sätz­lich das Python-Modul json ein­ge­bun­den werden, wie im Beispiel oben zu sehen ist. Strings und Dateien können dann mittels loads() oder load() in ein für Python lesbares Format um­ge­wan­delt werden.

Die find-Methode liefert nicht nur den an­ge­frag­ten Wert zurück, sondern auch weitere kon­tex­tu­el­le In­for­ma­tio­nen, wie zum Beispiel der Pfad zum gesuchten Wert. Diese In­for­ma­tio­nen werden in Form einer Liste zu­rück­ge­ge­ben, wobei der gesuchte Wert den Index 0 hat. Ent­spre­chend können Sie mit result[0].value den gesuchten Wert ausgeben lassen.

Im oben genannten Beispiel wurde beim Festlegen der Anfrage das Dol­lar­zei­chen verwendet. Dies ist ein atomarer Ausdruck, mit dem man auf das Root-Objekt der JSON verweist. Alle Ope­ra­to­ren und atomare Ausdrücke sind in folgender Tabelle auf­ge­lis­tet.

Ausdruck/Operator Bedeutung Beispiel Erklärung
$ Root-Objekt $.marcus.alter Greift auf dem Wert des Schlüs­sels “alter” aus dem Objekt “marcus” zu.
. Feld eines Objekts $.marcus Greift auf “marcus” zu, wobei “marcus” ein Feld des Root-Objekts ist.
.. Rekursive Suche nach einem Feld. Felder in Un­ter­ob­jek­ten werden auch un­ter­sucht. $.people..alter Gibt alle Vorkommen des Feldes “alter” in “people” und seine Un­ter­ob­jek­te zurück.
[x] Element in einem Array $.people[5] Greift auf das sechste Element (an Index 5) im Array “people” zu.
\* Platz­hal­ter für Zahl, wird meistens in Zu­sam­men­hang mit for-Schleifen verwendet $.people[\*] Greift auf ein Feld in “people” zu. In Kom­bi­na­ti­on mit einer for-Schleife würde jedes Feld der Reihe nach zu­rück­ge­ge­ben werden.

Zu­sätz­lich zu den Aus­drü­cken und Ope­ra­to­ren gibt es noch einige Filter, mit denen man seine Suche noch spe­zi­fi­scher gestalten kann. In der Python Im­ple­men­tie­rung von JSONPath lassen sich diese mit Python-Ope­ra­to­ren ver­knüp­fen. Alle Symbole, die in Zu­sam­men­hang mit Filtern verwendet werden können, sind mit Bei­spie­len in folgender Tabelle auf­ge­fasst.

Symbole Bedeutung Beispiel Erklärung
.[?(filter)] All­ge­mei­ne Syntax für Filter. Runde Klammern können weg­ge­las­sen werden. $.people[?(@.name == "Anne")] Greift auf Personen zu, deren Namen “Anne” ist.
@ Aktuell un­ter­such­tes Objekt, oft in Zu­sam­men­hang mit for-Schleifen verwendet. $.people[?(@.alter < 50)] Greift auf Felder in “people” zu, deren Wert für “alter” kleiner ist als 50.
<, >, <=, >=, == und != Ver­gleichs­ope­ra­to­ren, mit denen man bestimmte Such­ergeb­nis­se her­aus­fil­tern kann. $.people[@.alter < 50 & @.alter > 20] Greift auf Personen zu, deren Alter zwischen 20 und 50 Jahren liegt.
& Logisches UND. $.people[?(@.wohnort == 'Berlin' & @.alter > 40)] Greift auf Personen zu, die älter als 40 sind und in Berlin wohnen.
Hinweis

Wenn Sie Filter verwenden möchten, müssen Sie das Modul jsonpath_ng.ext einbinden, und beim Aufruf von parse() auf dieses verweisen.

An­wen­dungs­bei­spiel für Python JSONPath

import json
import jsonpath_ng as jp
# JSON-Daten als String
import json
import jsonpath_ng as jp
# JSON-Daten als String
data = """
{
    "staedte": [
        {
            "name": "Berlin",
            "bundesland": "Berlin",
            "einwohner": 3645000,
            "istHauptstadt": true,
            "bezirk Pankow": {
                "einwohner": 410000    
            }
        },
        {
            "name": "Hamburg",
            "bundesland": "Hamburg",
            "einwohner": 1841000,
            "istHauptstadt": false
        },
        {
            "name": "Muenchen",
            "bundesland": "Bayern",
            "einwohner": 1472000,
            "istHauptstadt": false
        },
        {
            "name": "Koeln",
            "bundesland": "Nordrhein-Westfalen",
            "einwohner": 1086000
        }
    ]
}
"""
# Konvertiere data von String zu Dictionary
json_data = json.loads(data)
# Anfrage: Namen aller Städte
query1 = jp.parse("staedte[*].name")
for match in query1.find(json_data):
    print(match.value)     # Ausgabe: Berlin, Hamburg, Muenchen, Koeln
# jsonpath_ng.ext importieren, um Filter anzuwenden
import jsonpath_ng.ext as jpx
# Anfrage: Namen aller Städte, die weniger als 1,5 Millionen Einwohner haben 
query2 = jpx.parse("$.staedte[?@.einwohner < 1500000].name")
for match in query2.find(json_data):
    print(match.value)     # Ausgabe: Muenchen, Koeln
# Alle Felder, die mit "einwohner" beschriftet sind 
query3 = jp.parse("$.staedte..einwohner")
match = query3.find(json_data)
for i in match:
    print(i.value)     # Ausgabe: 3645000, 410000, 1841000, 1472000, 1086000
# Die Namen aller Städte, die nicht "Berlin" heißen
query4 = jpx.parse('$.staedte[?(@.name != "Berlin")].name')
for match in query4.find(json_data):
    print(match.value)     # Ausgabe: Hamburg, Muenchen, Koeln
Python

In diesem Beispiel werden JSON-Daten als String angegeben und dann mittels loads() in ein Dic­tion­a­ry-Objekt um­ge­wan­delt. Im Root-Objekt ist nur ein einzelnes Array enthalten, das wiederum 4 Städte enthält. Jede Stadt hat 4 Felder, die die folgenden Daten enthalten:

  • Name der Stadt
  • Bun­des­land der Stadt
  • Anzahl an Einwohner
  • Ob die Stadt die Haupt­stadt Deutsch­lands ist oder nicht

Berlin hat als zu­sätz­li­ches Feld noch ein Objekt namens “Bezirk Pankow”, das selbst auch eine Ein­woh­ner­an­zahl enthält.

Nachdem die Daten in ein passendes Format um­ge­wan­delt wurden, werden 4 un­ter­schied­li­che Abfragen aus­ge­führt, deren Funk­ti­ons­wei­sen und Ausgaben im Beispiel als Kom­men­ta­re hin­ter­las­sen sind. Sicher fällt Ihnen auf, dass bei der dritten Anfrage fünf Werte zu­rück­ge­kom­men sind. Das liegt daran, dass der ..-Operator rekursiv nach passenden Feldern sucht. Das heißt, es werden sowohl alle Objekte un­ter­sucht als auch alle Kinder dieser Objekte. Ent­spre­chend ist die Ein­woh­ner­an­zahl von Pankow neben den Ein­woh­ner­an­zah­len der Städte auf­ge­lis­tet.

Tipp

Die Kom­bi­na­ti­on von JSON und Python ist ein viel­sei­ti­ges Werkzeug für die In­ter­net­pro­gram­mie­rung. Wenn Sie eine Web­an­wen­dung haben, die Sie schnell, leicht und direkt über Git ver­öf­fent­li­chen möchten, ist Deploy Now von IONOS die perfekte Lösung für Sie.

Zum Hauptmenü