Obsah
1. Rozhraní mezi nástrojem jq a programovacím jazykem Python
2. Balíček jq.py s implementací rozhraní mezi Pythonem a nástrojem jq
3. Rozhraní mezi Pythonem a nástrojem jq z pohledu programátora
4. Chování v případě chybného vstupu, obsluha výjimek
6. Použití uvozovek v dotazovacím jazyku nástroje jq
7. Ukázky nepatrně složitějších dotazů
8. Získání složitější datové struktury – slovníku nebo seznamu
9. Dotaz vracející seznam obsahující slovníky
10. Alternativní přístup k nástroji jq
11. Instalace balíčku pyjq a otestování, zda je ho možné naimportovat
12. Struktura volání jq přes rozhraní reprezentované balíčkem pyjq
13. Chování balíčku pyjq ve chvíli, kdy požadovaný prvek neexistuje
14. Použití uvozovek v dotazovacím jazyku
15. Ukázky nepatrně složitějších dotazů
16. Zřetězení dotazů s využitím znaku „|“
17. Přečtení složitější datové struktury – slovníku nebo seznamu
18. Ekvivalenty příkladů z první poloviny článku
19. Repositář s demonstračními příklady
1. Rozhraní mezi nástrojem jq a programovacím jazykem Python
V článku o užitečném nástroji jq jsme si řekli, že nástroj jq, který je používám pro zpracování dat uložených ve formátu JSON, je určen primárně pro spouštění z příkazové řádky, popř. ze shell skriptů. Ovšem kvůli velké popularitě tohoto nástroje (ostatně viz například počet hvězdiček přidělených jeho repositáři) vzniklo i rozhraní mezi jq a programovacím jazykem Python. Ve skutečnosti, abychom byli více přesní, vznikla dokonce dvě rozhraní reprezentovaná různě pojmenovanými balíčky. První balíček se jmenuje jq, ovšem kvůli rozlišení oproti původnímu nástroji jq se většinou používá jméno jq.py. A druhý podobně koncipovaný balíček, ovšem s odlišným API, se pro změnu jmenuje pyjq (nyní pochopitelně bez tečky).
Před použitím pythoních balíčků jq.py nebo pyjq je vhodné si nainstalovat binární balíček jq se stejně pojmenovaným nástrojem jq spustitelným z příkazové řádky. Podrobnostem jsme se věnovali minule, takže jen krátce – nejrychlejší bývá použití správce balíčků vaší distribuce Linuxu. V případě dnes již dosti muzeální Fedory 27 proběhne instalace následovně:
$ sudo dnf install jq Last metadata expiration check: 1:36:21 ago on Tue 04 Aug 2020 05:00:30 PM CEST. Dependencies resolved. ================================================================================ Package Arch Version Repository Size ================================================================================ Installing: jq x86_64 1.5-8.fc27 fedora 158 k Installing dependencies: oniguruma x86_64 6.6.1-1.fc27 fedora 178 k Transaction Summary ================================================================================ Install 2 Packages Total download size: 337 k Installed size: 1.1 M Is this ok [y/N]: y
2. Balíček jq.py s implementací rozhraní mezi Pythonem a nástrojem jq
Nejdříve se budeme věnovat balíčku pojmenovaném jq.py, jenž zpřístupňuje nástroj jq přímo programátorům používajícím Python. Tento balíček lze nainstalovat snadno, typicky nástrojem pip nebo pip3, popř. lze pochopitelně využít virtuální prostředí Pythonu:
$ pip3 install --user jq Collecting jq Downloading https://files.pythonhosted.org/packages/37/83/e1f7162986c228cc33768b9c53c1167cafe222f8d81f1325a27cfff42f47/jq-1.0.2-cp36-cp36m-manylinux1_x86_64.whl (502kB) 100% |████████████████████████████████| 512kB 1.5MB/s Installing collected packages: jq Successfully installed jq-1.0.2
Následně se můžeme přesvědčit, že je balíček jq.py skutečně dostupný pro vývojáře používající programovací jazyk Python. Následující skript by měl být spustitelný a měl by zobrazit nápovědu k balíčku jq.py:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq help(jq)
S přibližným výstupem:
Help on module jq: NAME jq FUNCTIONS all(...) compile(...) first(...) iter(...) jq(...) text(...) DATA __test__ = {} FILE
Pokud dojde k chybě při importu, zkontrolujte si, zda je jq.py nainstalován v adresáři, který je součástí seznamu cest, na kterých interpret Pythonu hledá balíčky:
import sys print(sys.path)
3. Rozhraní mezi Pythonem a nástrojem jq z pohledu programátora
V balíčku jq.py je k dispozici pouze několik funkcí a metod, které zprostředkovávají rozhraní mezi nástrojem jq a skriptem psaným v Pythonu. Jedná se o následující funkce a metody:
# | Funkce | Stručný popis |
---|---|---|
1 | compile() | (funkce) program psaný v DSL nástroje jq je přeložen a vrácen ve formě objektu |
2 | input() | metoda, které se předají vstupní data buď ve formě textu nebo JSON objektu |
3 | first() | získání prvního výsledku aplikace dotazu na JSON data |
4 | all() | získání všech výsledků aplikace dotazu na JSON data |
5 | text() | získání výsledků ve formě textu (a nikoli slovníku nebo seznamu) |
6 | iter() | (funkce) získání iterátoru pro procházení jednotlivými prvky výsledku |
Podívejme se nyní na několik základních příkladů použití balíčku jq.py.
Přečtení vstupního souboru do řetězce (tedy do čistého textu), aplikace dotazu na text a získání výsledku ve formě seznamu:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq with open("openapi.json") as fin: content = fin.read() print(jq.compile(".openapi").input(text=content).all())
Výsledek:
['3.0.0']
Přečtení vstupního souboru do objektu JSON, aplikace dotazu na JSON a získání výsledku ve formě seznamu:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq import json with open("openapi.json") as fin: content = json.load(fin) print(jq.compile(".openapi").input(content).all())
Výsledek:
['3.0.0']
Dtto, ale tentokrát získáme výsledek ve formě čistého textu a nikoli seznamu:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq with open("openapi.json") as fin: content = fin.read() print(jq.compile(".openapi").input(text=content).text())
Výsledek:
"3.0.0"
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq import json with open("openapi.json") as fin: content = json.load(fin) print(jq.compile(".openapi").input(content).text())
Výsledek:
"3.0.0"
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq import json with open("openapi.json") as fin: content = json.load(fin) value = jq.compile(".security").input(content).text() print(value) print(type(value))
Výsledek:
[] <class 'str'>
4. Chování v případě chybného vstupu, obsluha výjimek
Nyní se pokusme zpracovat chybný soubor nazvaný „broken.json“, který neobsahuje korektní obsah v JSONu. Obsah tohoto souboru nelze přečíst prostředky poskytovanými standardním balíčkem json, takže vyzkoušíme, co se stane při přečtení obsahu souboru do řetězce s předáním tohoto řetězce zkompilovanému dotazu balíčku jq.py:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq with open("broken.json") as fin: content = fin.read() print(jq.compile(".openapi").input(text=content).all())
Při pokusu o spuštění výše uvedeného skriptu dojde k výjimce, která naznačuje, že se vzniklá chyba v binárním balíčku jq pouze převede na výjimku a předá se volajícímu kódu:
$ ./04_no_error_handling.py Traceback (most recent call last): File "./04_no_error_handling.py", line 11, in <module> print(jq.compile(".openapi").input(text=content).all()) File "jq.pyx", line 211, in jq._ProgramwithInput.all File "jq.pyx", line 242, in jq._ResultIterator.__next__ File "jq.pyx", line 248, in jq._ResultIterator._next_string File "jq.pyx", line 275, in jq._ResultIterator._ready_next_input ValueError: parse error: Expected separator between values at line 11, column 15
Takovou výjimku pochopitelně můžeme velmi snadno odchytit a nějakým způsobem na ni zareagovat (minimálně vypsat informace do logu, když už nic jiného):
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq with open("broken.json") as fin: content = fin.read() try: print(jq.compile(".openapi").input(text=content).all()) except Exception as e: print(e)
Chování takto upraveného skriptu po jeho spuštění již bude dosti odlišné:
$ ./05_error_handling.py parse error: Expected separator between values at line 11, column 15
5. Metoda first
Víme již, že pokud je vstup do jq zpracován korektně, můžeme výstup získat buď ve formě seznamu (metoda all) nebo textu (metoda text). Mnohdy je ovšem výsledkem dotazu jediná hodnota (prvek z původního JSONu, popř. nějakým způsobem transformovaný prvek). A hodnotu tohoto jediného prvku získáme metodou first, což je ukázáno na dalším demonstračním příkladu:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq import json with open("openapi.json") as fin: content = json.load(fin) print(jq.compile(".openapi").input(content).first()) print(jq.compile(".info.description").input(content).first()) print(jq.compile(".tags").input(content).first())
S výsledkem:
3.0.0 A very simple REST API service []
O tom, že se skutečně jedná o prvky se správným typem (v Pythonu) se opět můžeme přesvědčit po úpravě skriptu:
with open("openapi.json") as fin: content = json.load(fin) print(type(jq.compile(".openapi").input(content).first())) print(type(jq.compile(".info.description").input(content).first())) print(type(jq.compile(".tags").input(content).first()))
Nyní se namísto hodnot získaných prvků vypíšou jejich typy:
<class 'str'> <class 'str'> <class 'list'>
V případě, že prvek není nalezen (je zadána špatná cesta, resp. cesta neodpovídající obsahu JSONu), vrátí se hodnota None:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq import json with open("openapi.json") as fin: content = json.load(fin) print(jq.compile(".non_existing_key").input(content).first())
Vytiskne se:
None
Který je skutečně správného typu:
with open("openapi.json") as fin: content = json.load(fin) print(type(jq.compile(".non_existing_key").input(content).first()))
S výsledkem:
<class 'NoneType'>
6. Použití uvozovek v dotazovacím jazyku nástroje jq
Balíček jq.py ve skutečnosti tvoří pouze velmi úzkou vrstvu mezi Pythonem a nástrojem jq. Například se vlastně žádným způsobem nemodifikuje dotaz (query) zapisovaný v doménově specifickém jazyku (DSL) nástroje jq. Musíme si dát pozor především na správné použití uvozovek ve chvíli, kdy se přistupuje ke klíči, který například obsahuje lomítko či jiný specifický znak (JSON je sice pojmenován podle JavaScriptu, ovšem jeho syntaxe je v tomto ohledu volnější). Podívejme se nyní na demonstrační příklad, ve kterém se přistupuje ke zvýrazněné části JSONu:
{ ... ... ... "paths": { "/": { "get": { ... ... ... } } } }
V dotazu se tedy musí vyskytovat .paths./, což ovšem není korektní zápis. Ve skutečnosti musíme lomítko (poslední část dotazu) zapsat do uvozovek. Pokud do uvozovek vložíme celý dotaz, vrátí se samotný dotaz jako výsledek (řetězec); pokud uvozovky neuvedeme vůbec, dojde k chybě při pokusu o spuštění skriptu:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq import json with open("openapi.json") as fin: content = json.load(fin) print("-----------------------------") print(jq.compile('.paths."/"').input(content).first()) print("-----------------------------") print(jq.compile('".paths./"').input(content).first()) print("-----------------------------") print(jq.compile('.paths./').input(content).first())
Po spuštění tohoto demonstračního příkladu je jasně patrné, jakým způsobem jsou výsledné dotazy zpracovány, resp. v posledním případě nezpracovány:
----------------------------- {'get': {'summary': 'Returns valid HTTP 200 ok status when the service is ready', 'description': '', 'parameters': [], 'operationId': 'main', 'responses': {'default': {'description': 'Default response'}}}} ----------------------------- .paths./ ----------------------------- Traceback (most recent call last): File "./08_escape_characters.py", line 17, in print(jq.compile('.paths./').input(content).first()) File "jq.pyx", line 56, in jq.compile File "jq.pyx", line 160, in jq._Program.__cinit__ File "jq.pyx", line 131, in jq._JqStatePool.__cinit__ File "jq.pyx", line 84, in jq._compile File "jq.pyx", line 72, in jq._compile File "jq.pyx", line 78, in jq._compile ValueError: jq: error: syntax error, unexpected '/', expecting FORMAT or QQSTRING_START (Unix shell quoting issues?) at , line 1: .paths./ jq: 1 compile error
7. Ukázky nepatrně složitějších dotazů
Získání informace o licenci, pod kterou je soubor vydán:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq import json with open("openapi.json") as fin: content = json.load(fin) print(jq.compile(".info.license.name").input(content).first())
S výsledkem:
Apache 2.0
Získání souhrnných popisů všech endpointů s HTTP požadavkem typu GET:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq import json with open("openapi.json") as fin: content = json.load(fin) summaries = jq.compile(".paths[] | .get.summary").input(content).all() for summary in summaries: print(summary)
Nyní se vypíše:
Returns valid HTTP 200 ok status when the service is ready Read list of all clusters from database and return it to a client Read cluster specified by its ID and return it to a client Search for a cluster specified by its ID or name
Dtto, ovšem pro HTTP požadavky typu DELETE (ten existuje pouze pro jediný koncový bod):
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq import json with open("openapi.json") as fin: content = json.load(fin) summaries = jq.compile(".paths[] | .delete.summary").input(content).all() for summary in summaries: print(summary)
S výsledky:
None None Delete a cluster specified by its ID None
8. Získání složitější datové struktury – slovníku nebo seznamu
V případě, že je výsledkem dotazu obsah uzlu s dalšími poduzly nebo polem, vrátí se tento obsah ve formě příslušné datové struktury Pythonu, což si ostatně ukážeme na dalším příkladu, který obsahuje tentýž dotaz, jednou ovšem zapsaný v dvojitých uvozovkách (interně vyžaduje „quotování“ vnitřních uvozovek) a podruhé zapsaný s uvozovkami jednoduchými:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq import json from pprint import pprint with open("openapi.json") as fin: content = json.load(fin) search = jq.compile(".paths.\"/client/cluster/search\"").input(content).first() pprint(search) print("----------------------------------------------------------------------------") search = jq.compile('.paths."/client/cluster/search"').input(content).first() pprint(search)
Výsledek je v obou případech vždy stejný:
{'get': {'description': '', 'operationId': 'searchCluster', 'parameters': [{'allowEmptyValue': True, 'description': 'Cluster ID', 'in': 'query', 'name': 'id', 'required': False, 'schema': {'type': 'string'}}, {'allowEmptyValue': True, 'description': 'Cluster name', 'in': 'query', 'name': 'name', 'required': False, 'schema': {'type': 'string'}}], 'responses': {'default': {'description': 'Default response'}}, 'summary': 'Search for a cluster specified by its ID or name'}} ---------------------------------------------------------------------------- {'get': {'description': '', 'operationId': 'searchCluster', 'parameters': [{'allowEmptyValue': True, 'description': 'Cluster ID', 'in': 'query', 'name': 'id', 'required': False, 'schema': {'type': 'string'}}, {'allowEmptyValue': True, 'description': 'Cluster name', 'in': 'query', 'name': 'name', 'required': False, 'schema': {'type': 'string'}}], 'responses': {'default': {'description': 'Default response'}}, 'summary': 'Search for a cluster specified by its ID or name'}}
V dalším příkladu je výsledkem dotazu seznam seznamů a ve druhé části seznam pravdivostních hodnot True nebo False. Seznam obsahuje pro každý koncový bod další seznam s podporovanými metodami, který následně spojíme do řetězce tak, aby byly názvy jednotlivých metod odděleny čárkami:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq import json with open("openapi.json") as fin: content = json.load(fin) for endpoint in jq.compile('.paths[]').input(content).all(): print(",".join(endpoint.keys())) print("-------------------------") for has_get in jq.compile('.paths[] | has("get")').input(content).all(): print(has_get)
Výsledek zobrazený po spuštění tohoto demonstračního příkladu:
get x-temp,get get,post,delete get ------------------------- True True True True
Dtto, ovšem ve druhém dotazu a na něj navázané programové smyčce zobrazíme informaci, zda daný koncový bod podporuje HTTP metodu DELETE:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq import json with open("openapi.json") as fin: content = json.load(fin) for endpoint in jq.compile('.paths[]').input(content).all(): print(",".join(endpoint.keys())) print("-------------------------") for has_delete in jq.compile('.paths[] | has("delete")').input(content).all(): print(has_delete)
S výsledky:
get x-temp,get get,post,delete get ------------------------- False False True False
9. Dotaz vracející seznam obsahující slovníky
V posledním demonstračním příkladu, který je založen na balíčku jq.py použijeme dotaz vracející seznam, jehož prvky jsou slovníky. Dotaz totiž získá všechny parametry koncového bodu „/client/cluster/search“ pro HTTP metodu GET:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou jq.py import jq import json from pprint import pprint with open("openapi.json") as fin: content = json.load(fin) for parameters in jq.compile('.paths."/client/cluster/search".get.parameters').input(content).all(): pprint(parameters)
Po spuštění tohoto skriptu by se měl zobrazit seznam s dvojicí prvků, přičemž každý prvek je slovníkem. Povšimněte si, že pravdivostní hodnoty jsou z JSONu převedeny na skutečné pravdivostní hodnoty jazyka Python:
[{'allowEmptyValue': True, 'description': 'Cluster ID', 'in': 'query', 'name': 'id', 'required': False, 'schema': {'type': 'string'}}, {'allowEmptyValue': True, 'description': 'Cluster name', 'in': 'query', 'name': 'name', 'required': False, 'schema': {'type': 'string'}}]
10. Alternativní přístup k nástroji jq
V první polovině článku jsme se primárně zabývali balíčkem jq.py, který poskytuje vývojářům pracujícím v programovacím jazyku Python rozhraní pro nástroj jq. Víme již, že kromě balíčku jq.py existuje i alternativní balíček nazvaný pro změnu pyjq. Ve druhé části dnešního článku si tedy ukážeme základní způsob použití tohoto balíčku, který se ovládá nepatrně odlišným způsobem. Ovšem základní princip zůstává stejný – získat ze vstupního JSONu datovou strukturu nebo seznam struktur a tu dále nějakým způsobem dále zpracovat.
11. Instalace balíčku pyjq a otestování, zda je ho možné naimportovat
Instalaci balíčku pyjq opět provedeme pomocí nástroje pip, popř. pip3. Instalace bude nepatrně delší, protože se bude překládat i nativní část balíčku (počítejte s cca dvaceti sekundami):
$ pip3 install --user pyjq Collecting pyjq Downloading https://files.pythonhosted.org/packages/a5/7c/b7fdc7b9653d5f05552cb08b6e9883db13db21ca0c8b0cd100e5a5ed3a35/pyjq-2.4.0.tar.gz (2.0MB) 100% |████████████████████████████████| 2.0MB 723kB/s Installing collected packages: pyjq Running setup.py install for pyjq ... done Successfully installed pyjq-2.4.0
A opět si můžeme otestovat, zda je možné naimportovat balíček pyjq do Pythonovského skriptu:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou pyjq import pyjq help(pyjq)
Tento skript by měl po svém spuštění vypsat (dosti stručnou) nápovědu:
Help on module pyjq: NAME pyjq DATA __all__ = [] FILE /home/ptisnovs/.local/lib/python3.6/site-packages/pyjq.py
12. Struktura volání jq přes rozhraní reprezentované balíčkem pyjq
Ze zdrojových kódů následujících dvou skriptů je patrné, že se struktura volání mezi balíčky jq.py a pyjq odlišuje. Při použití balíčku pyjq je nejdříve nutné přeložit dotaz (naprosto stejně pojmenovanou funkcí – konstruktorem), ovšem následně se již přímo volá metoda all nebo first, které se předá již načtený obsah JSON souboru. Povšimněte si, že není podporováno přímé zpracování dat z textového souboru (resp. z řetězce) – vždy se použije standardní balíček json.
Volání ve chvíli, kdy vyžadujeme získání většího množství hodnot:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou pyjq import pyjq import json with open("openapi.json") as fin: content = json.load(fin) print(pyjq.compile(".openapi").all(content))
V tomto konkrétním případě se vrátí seznam s jedinou hodnotou, ovšem stále se bude jednat o seznam:
['3.0.0']
Volání ve chvíli, kdy vyžadujeme přečtení jediné hodnoty, tj. buď prvního prvku nebo jediného prvku odpovídajícího dotazu:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou pyjq import pyjq import json with open("openapi.json") as fin: content = json.load(fin) print(pyjq.compile(".openapi").first(content))
Výsledek je tvořen řetězcem:
3.0.0
Pozor ovšem na to, že čistě textový vstup nelze zpracovat:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou pyjq import pyjq with open("openapi.json") as fin: content = fin.read() print(pyjq.compile(".openapi").all(content))
Při pokusu o zpracování dojde k chybě:
Traceback (most recent call last): File "./14_no_text_processing.py", line 11, in <module> print(pyjq.compile(".openapi").all(content)) File "_pyjq.pyx", line 213, in _pyjq.Script.all _pyjq.ScriptRuntimeError: Cannot index string with string "openapi"
13. Chování balíčku pyjq ve chvíli, kdy požadovaný prvek neexistuje
V případě, že požadovaný prvek neexistuje (tedy většinou tehdy, kdy není nalezen příslušný klíč), vrací balíček pyjq buď hodnotu None nebo seznam s jediným prvkem None. Otestujme si nejprve první případ, tj. pokus o přečtení jediného prvku, který ovšem v JSON souboru neexistuje:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou pyjq import pyjq import json with open("openapi.json") as fin: content = json.load(fin) print(pyjq.compile(".foobar").first(content))
Prvek s klíčem foobar v JSON souboru nelze nalézt, proto se vrátí hodnota:
None
Ve druhém případě budeme chtít získat více prvků, nikoli prvek jediný, takže se použije metoda all:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou pyjq import pyjq import json with open("openapi.json") as fin: content = json.load(fin) print(pyjq.compile(".foobar").all(content))
Výsledkem je v tomto případě seznam s jediným prvkem obsahujícím hodnotu None:
[None]
14. Použití uvozovek v dotazovacím jazyku
V šesté kapitole jsme si řekli, že pokud klíče obsahují některé speciální znaky, zejména lomítka, je nutné tyto názvy klíčů (ovšem nikoli celý dotaz!) umístit do uvozovek. Pokud to neuděláme, dojde k chybě. Obě možnosti ilustruje následující demonstrační příklad, který vznikl přímým přepisem příkladu ze šesté kapitoly:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou pyjq import pyjq import json with open("openapi.json") as fin: content = json.load(fin) print("-----------------------------") print(pyjq.compile('.paths."/"').first(content)) print("-----------------------------") print(pyjq.compile('".paths./"').first(content)) print("-----------------------------") print(pyjq.compile('.paths./').first(content))
První výsledek je korektní, ve druhém případě je dotaz chápán jako konstantní řetězec a v případě třetím (zcela chybějící uvozovky) dojde k chybě při běhu:
----------------------------- {'get': {'summary': 'Returns valid HTTP 200 ok status when the service is ready', 'description': '', 'parameters': [], 'operationId': 'main', 'responses': {'default': {'description': 'Default response'}}}} ----------------------------- .paths./ ----------------------------- Traceback (most recent call last): File "./06_escape_characters.py", line 17, in <module> print(pyjq.compile('.paths./').first(content)) File "/home/ptisnovs/.local/lib/python3.6/site-packages/pyjq.py", line 19, in compile library_paths=library_paths) File "_pyjq.pyx", line 190, in _pyjq.Script.__init__ ValueError: jq: error: syntax error, unexpected '/', expecting FORMAT or QQSTRING_START (Unix shell quoting issues?) at , line 1: .paths./ jq: 1 compile error
15. Ukázky nepatrně složitějších dotazů
Získání informace o licenci, pod kterou je soubor vydán (viz též sedmou kapitolu):
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou pyjq import pyjq import json with open("openapi.json") as fin: content = json.load(fin) print(pyjq.compile(".info.license.name").first(content))
S výsledkem:
Apache 2.0
16. Zřetězení dotazů s využitím znaku „|“
Získání souhrnných popisů všech endpointů s HTTP požadavkem typu GET:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou pyjq import pyjq import json with open("openapi.json") as fin: content = json.load(fin) summaries = pyjq.compile(".paths[] | .get.summary").all(content) for summary in summaries: print(summary)
Po spuštění tohoto demonstračního příkladu se vypíše:
Returns valid HTTP 200 ok status when the service is ready Read list of all clusters from database and return it to a client Read cluster specified by its ID and return it to a client Search for a cluster specified by its ID or name
Dtto, ovšem pro HTTP požadavky typu DELETE (ten existuje pouze pro jediný koncový bod):
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou pyjq import pyjq import json with open("openapi.json") as fin: content = json.load(fin) summaries = pyjq.compile(".paths[] | .delete.summary").all(content) for summary in summaries: print(summary)
Výsledky:
None None Delete a cluster specified by its ID None
Pro jistotu se podíváme i na typy vrácených hodnot:
with open("openapi.json") as fin: content = json.load(fin) summaries = pyjq.compile(".paths[] | .delete.summary").all(content) for summary in summaries: print(summary, type(summary))
Výsledky:
None <class 'NoneType'> None <class 'NoneType'> Delete a cluster specified by its ID <class 'str'> None <class 'NoneType'>
17. Přečtení složitější datové struktury – slovníku nebo seznamu
Opět si ukažme, jak je možné získat složitější datovou strukturu z JSON souboru. Většinou se bude jednat o slovník nebo seznam, v našem případě slovník:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou pyjq import pyjq import json from pprint import pprint with open("openapi.json") as fin: content = json.load(fin) search = pyjq.compile(".paths.\"/client/cluster/search\"").first(content) pprint(search) print("----------------------------------------------------------------------------") search = pyjq.compile('.paths."/client/cluster/search"').first(content) pprint(search)
Oba dotazy jsou totožné, ovšem druhý dotaz je díky použití jednoduchých i dvojitých uvozovek mnohem čitelnější:
{'get': {'description': '', 'operationId': 'searchCluster', 'parameters': [{'allowEmptyValue': True, 'description': 'Cluster ID', 'in': 'query', 'name': 'id', 'required': False, 'schema': {'type': 'string'}}, {'allowEmptyValue': True, 'description': 'Cluster name', 'in': 'query', 'name': 'name', 'required': False, 'schema': {'type': 'string'}}], 'responses': {'default': {'description': 'Default response'}}, 'summary': 'Search for a cluster specified by its ID or name'}} ---------------------------------------------------------------------------- {'get': {'description': '', 'operationId': 'searchCluster', 'parameters': [{'allowEmptyValue': True, 'description': 'Cluster ID', 'in': 'query', 'name': 'id', 'required': False, 'schema': {'type': 'string'}}, {'allowEmptyValue': True, 'description': 'Cluster name', 'in': 'query', 'name': 'name', 'required': False, 'schema': {'type': 'string'}}], 'responses': {'default': {'description': 'Default response'}}, 'summary': 'Search for a cluster specified by its ID or name'}}
18. Ekvivalenty příkladů z první poloviny článku
Demonstrační příklady, na nichž jsme si ukázali některé možnosti balíčku jq.py, lze pochopitelně velmi snadno přepsat tak, aby používaly alternativní balíček pyjq:
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou pyjq import pyjq import json with open("openapi.json") as fin: content = json.load(fin) for endpoint in pyjq.compile('.paths[]').all(content): print(",".join(endpoint.keys())) print("-------------------------") for has_get in pyjq.compile('.paths[] | has("get")').all(content): print(has_get)
Výsledek:
get x-temp,get get,post,delete get ------------------------- True True True True
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou pyjq import pyjq import json with open("openapi.json") as fin: content = json.load(fin) for endpoint in pyjq.compile('.paths[]').all(content): print(",".join(endpoint.keys())) print("-------------------------") for has_delete in pyjq.compile('.paths[] | has("delete")').all(content): print(has_delete)
Výsledek:
get x-temp,get get,post,delete get ------------------------- False False True False
#!/usr/bin/env python3 # vim: set fileencoding=utf-8 # Demonstrační příklad k článku: # Zpracování dat uložených ve formátu JSON knihovnou pyjq import pyjq import json from pprint import pprint with open("openapi.json") as fin: content = json.load(fin) for parameters in pyjq.compile('.paths."/client/cluster/search".get.parameters').all(content): pprint(parameters)
Výsledek:
[{'allowEmptyValue': True, 'description': 'Cluster ID', 'in': 'query', 'name': 'id', 'required': False, 'schema': {'type': 'string'}}, {'allowEmptyValue': True, 'description': 'Cluster name', 'in': 'query', 'name': 'name', 'required': False, 'schema': {'type': 'string'}}]
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do nového Git repositáře, který je dostupný na adrese https://github.com/tisnik/most-popular-python-libs. V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně několik jednotek kilobajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady a jejich části, které naleznete v následující tabulce:
20. Odkazy na Internetu
- Zpracování dat reprezentovaných ve formátu JSON nástrojem jq
https://www.root.cz/clanky/zpracovani-dat-reprezentovanych-ve-formatu-json-nastrojem-jq/ - Balíček jq.py na PyPi
https://pypi.org/project/jq/ - Balíček pyjq na PyPi
https://pypi.org/project/pyjq/ - Repositář projektu jq (GitHub)
https://github.com/stedolan/jq - GitHub stránky projektu jq
https://stedolan.github.io/jq/ - 5 modern alternatives to essential Linux command-line tools
https://opensource.com/article/20/6/modern-linux-command-line-tools - Návod k nástroji jq
https://stedolan.github.io/jq/tutorial/ - jq Manual (development version)
https://stedolan.github.io/jq/manual/ - Introducing JSON
https://www.json.org/json-en.html - jq.py: a lightweight and flexible JSON processor
https://github.com/mwilliamson/jq.py - Discover how to use jq, a JSON manipulation command line, with GeoJSON
https://webgeodatavore.com/jq-json-manipulation-command-line-with-geojson.html - Reshaping JSON with jq
https://programminghistorian.org/en/lessons/json-and-jq - Python bindings for jq
https://pypi.org/project/jq/ - edn
https://github.com/edn-format/edn - Why use JSON over XML?
https://www.sitepoint.com/json-vs-xml/ - XML and XPath
https://www.w3schools.com/XML/xml_xpath.asp - XPath (Wikipedia)
https://en.wikipedia.org/wiki/XPath - RFC7159
https://www.ietf.org/rfc/rfc7159.txt