JSON Schema v praxi - Zdroják
Transkript
JSON Schema v praxi - Zdroják Stránka 1 JSON Schema v praxi Články - Honza Javorek (http://www.zdrojak.cz/autori/honza-javorek/) - Různé (http://www.zdrojak.cz/ruzne/) - 14.5.2014 Historie je plná příběhů o tom, jak zaostalé kmeny dobudou vyspělou civilizaci, její výdobytky zavrhnou a v zemi následně zavládne několik staletí postupného znovuvynalézání. Totéž dnes sledujeme v přímém přenosu – JSON byl velkým protestem proti XML, jenže jak dospívá jeho vlastní ekosystém, přichází se na to, že některé staré nápady možná až tak špatné nebyly. Jedním z nich je koncept schématu – způsobu, jak zapsat, jakou strukturu a typy by měla určitá data mít. V dnešním článku se dovíte, jak můžete snadno JSON Schema využít ve své aplikaci a usnadnit si tak práci s validací dat. Jak bylo naznačeno v úvodu článku, schéma popisuje strukturu a typy, jaké by měla data mít. XML i JSON jsou oba velice obecné formáty, takže je v nich možné zapsat téměř cokoliv. Chceme-li si data jen pro vlastní potřebu uložit a zase je načíst, schéma tolik neoceníme. Pokud bychom je ale rádi s někým sdíleli nebo je od někoho přijímali, bylo by už dobré druhé straně sdělit, jak mají vlastně vypadat – kde má být jaké políčko a jestli v něm má být číslo, nebo třeba seznam řetězců. To lze učinit buď psanou dokumentací, nebo strojovým popisem – schématem. Výhodami strojového popisu je jednoznačnost a možnost vůči němu snadno a automaticky data validovat. Nejlepší pro protistranu samozřejmě je, když jí nabídneme obě varianty – jak popisnou dokumentaci, tak schéma. Příklad ze života – užití schéma pro export z e-shopu Představte si, že programujete e-shop a chcete v něm mít XML export pro Heureku. Najdete si dokumentaci (http://sluzby.heureka.cz/napoveda/xml-feed/), která vám dává představu o podobě dat, jaké máte posílat, a dáte se do programování. Jenže lidský popis není vše – někdy jsou v něm nejasné formulace a rozhodně se vůči němu dost špatně automaticky validuje. Často se tak stane, že máte ve svém systému chybu, ta vygeneruje špatné XML, na Heurece to udělá neplechu a vám pak utíkají milionové tržby, ani o tom nevíte. Kdyby Heureka publikovala přesné schéma (http://www.kosek.cz/xml/schema/) pro svůj XML feed, mohl by si každý automaticky zkontrolovat, zda produkuje správně strukturovaná data – tedy zda dobře pochopil dokumentaci a neudělal žádnou chybu při implementaci. Stejně tak i Heureka by mohla podle téhož schéma snadno validovat příchozí XML a upozorňovat e-shopy na chyby. (Chybějící schéma se dnes dohání externími nástroji (http://www.mergado.cz/audit-xml).) JSON Schema Ten samý problém lze nyní elegantně řešit i pro JSON díky JSON Schema (http://json-schema.org/). Pokud vás hned z hlavy nenapadají situace, kde se to může hodit, zkusím jich pár nabídnout: Můžete schématem popsat své API, jako to udělalo Heroku (https://blog.heroku.com/archives/2014/1/8/json_schema_for_heroku_platform_api). Budete systematicky validovat data, která do vašeho API přicházejí. Používáte Apiary (http://apiary.io/)? I tam je podpora pro JSON Schema (http:// support.apiary.io/knowledgebase/articles/147279-json-schema-validation)! Počkáte si, až bude v HTML možné (https://darobin.github.io/formic/specs/json/) posílat formuláře s enctype="application/json" a pak si je budete přes schéma validovat. K jedné definici pravidel dostanete zdarma dvě implementace – podle téhož schématu můžete validovat jak na serveru, tak i v JavaScriptu na klientovi. Máte aplikaci s konfiguračním souborem v JSONu a chcete dát uživateli příjemnější chybovou hlášku, pokud se někde splete, než jen Config file invalid, see docs. Nebudu vás v tomto článku zásobit příklady, jak má JSON Schema vypadat (http://json-schema.org/example1.html), ani opisovat pěknou příručku, která podrobně celou technologii vysvětluje (https://spacetelescope.github.io/understanding-json-schema/). Chtěl bych vám prakticky ukázat, jak můžete jednoduše JSON Schema včlenit do své aplikace. http://www.zdrojak.cz/clanky/json-schema-praxi/ 13.3.2015 11:36:41 JSON Schema v praxi - Zdroják Stránka 2 Knihovny pro validaci Kontrolu podle schéma samozřejmě nebudeme dělat ručně – prvním krokem tedy bude nalezení vhodné knihovny pro náš jazyk. Moje příklady budou v Pythonu, takže se podívám na seznam software pro JSON Schema (http://json-schema.org/implementations.html) a vyberu si tu nejpoužívanější, jsonschema (https://github.com/Julian/jsonschema). Pojďme se v interaktivním Pythonu podívat, jak práce s knihovnou vypadá: $ pip install jsonschema $ python >>> schema = { ... 'type' : 'object', ... 'properties' : { ... 'age' : {'type' : 'number'}, ... 'name' : {'type' : 'string'}, ... }, ... } Takto jsme si připravili jednoduché schéma. Není to samozřejmě přímo JSON, ale jeho reprezentace v Pythonu pomocí datové struktury dict . Nyní zkusme oproti tomuto schéma validovat nějaká data: >>> from jsonschema import validate >>> validate({'name' : 'Honza', 'age' : 42}, schema) Výstupem posledního řádku nebude nic. Což je dobře, protože funkce validate nic neprodukuje, pokud ji nakrmíme platným vstupem. Zkusme ji trošku pozlobit: >>> validate({'name' : 'Honza', 'age' : 'Life, the Universe and Everything'}, schema) Traceback (most recent call last): ... ValidationError: 'Life, the Universe and Everything' is not of type 'number' Failed validating 'type' in schema['properties']['age']: {'type': 'number'} On instance['age']: 'Life, the Universe and Everything' Vstup neprošel – přesně jak jsme očekávali. validate vyhodilo výjimku, z níž můžeme dokonce zjistit nejrůznější detaily o tom, kde a co přesně nesedí. Takovéto jednoduché použití najdete samozřejmě i na první stránce v dokumentaci. My si dále na vlastním příkladě ukážeme, jak lze schémata načítat ze souboru, jak je vnořovat do sebe a proč je důležité, aby se naše knihovna nezastavila u první chyby. Praktická ukázka – validace vstupních dat ve vašem API Nejdříve si připravíme kousek našeho fiktivního API. K tomu budeme potřebovat webový framework – vezměme tedy něco hodně jednoduchého, řekněme Flask (http://flask.pocoo.org/). Přibereme si k němu i nějaký testovací nástroj, ať můžeme rychle odzkoušet, zda nám vše funguje: $ pip install flask pytest V souboru web.py načrtneme jednoduché view, které bude odpovídat na POST požadavky a přijímat na cestě /users data k založení nového uživatele: http://www.zdrojak.cz/clanky/json-schema-praxi/ 13.3.2015 11:36:41 JSON Schema v praxi - Zdroják Stránka 3 from uuid import uuid4 as uuid from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/users', methods=['POST']) def users(): data = request.get_json() data['id'] = uuid() # predstirame ukladani do databaze return jsonify(data), 201 if __name__ == '__main__': app.run(debug=True, host='0.0.0.0') Následně si do souboru tests.py napíšeme test, který odesílá data na adresu /users a snaží se nového uživatele založit: import json, pytest @pytest.fixture def test_client(): from web import app app.config['TESTING'] = True return app.test_client() def test_send_data(test_client): data_in = {'name': 'Honza', 'age': 42} response = test_client.post('/users', data=json.dumps(data_in), content_type='application/json') assert response.status_code == 201 data_out = json.loads(response.data) assert data_out['id'] assert data_out['name'] == data_in['name'] assert data_out['age'] == data_in['age'] Takovýto test projde – spustíme-li v konzoli py.test tests.py , bude na nás vše zeleně svítit. Pokud si aplikaci nastartujete přes python web.py , najdete ji v prohlížeči na http://localhost:5000/users , ale nic moc kloudného vám tam asi neodpoví, protože vůbec neobsluhujeme metodu GET. Lepší to bude třeba s curl : $ curl -X POST -H 'Content-Type: application/json' -d '{"name":"Honza","age":42}' 'http://localhost:5000/users' { "id": "79e015d7-a3f6-4619-867a-9c8fcf6f0d56", "age": 42, "name": "Honza" } Kód k této fázi naleznete zde (https://github.com/honzajavorek/zdrojak-json-schema/commit/51f486bc8dded35e8b786064d9eb6b5386d1675a) (odkaz přímo na commit). Validujeme! Kostru máme hotovou. Nyní zajistíme, aby nám do aplikace neproniklo nic, co neodpovídá naším požadavkům. Nejdříve si do souboru user.json sepíšeme jednoduché schéma: http://www.zdrojak.cz/clanky/json-schema-praxi/ 13.3.2015 11:36:41 JSON Schema v praxi - Zdroják Stránka 4 { "$schema": "http://json-schema.org/schema#", "title": "User", "type": "object", "properties": { "age": {"type": "integer"}, "name": {"type": "string"} }, "required": ["name"], "additionalProperties": false } Jak vidíme, data reprezentující uživatele mají mít pole age pro věk, jejíž hodnotou bude číslo, a pole name pro jméno, jehož hodnotou má být řetězec. Jméno bude přitom povinné a nedovolujeme nastavit žádná další pole. Nyní začneme připravovat validaci pro naše view: import json from jsonschema import Draft4Validator as Validator def validate(schema_filename, data): with open(schema_filename) as f: schema = json.load(f) # cteme JSON Schema primo ze souboru Validator.check_schema(schema) # zkontroluje schema nebo vyhodi vyjimku validator = Validator(schema) return validator.iter_errors(data) # vraci chyby jednu po druhe Nepoužili jsme funkci validate ze začátku článku, ale píšeme si vlastní, na míru. Dovolí nám to později lépe kontrolovat proces validace. Vybíráme si specifikaci JSON Schema, s níž chceme pracovat – v tomto případě Draft4 (http://tools.ietf.org/html/draft-zyp-json-schema-04). Nejdříve čteme schéma ze souboru, parsujeme jej a kontrolujeme, zda je ono samo vůbec v pořádku. Následně podle něj validujeme data a vracíme iterátor s jednotlivými chybami. Díky tomu, že se naše knihovna při použití iter_errors nezastaví na první chybě, budeme moci uživateli našeho API sdělit všechny nedostatky jím zaslaných dat najednou. Podívejme se, jak bude vypadat samotné view a testy: import os @app.route('/users', methods=['POST']) def users(): data = request.get_json() schema_filename = os.path.join(app.root_path, 'user.json') errors = [error.message for error in validate(schema_filename, data)] if errors: return jsonify(errors=errors), 400 data['id'] = uuid() # predstirame ukladani do databaze return jsonify(data), 201 def test_invalid_data(test_client): data_in = {'id': 2, 'name': 'Honza', 'age': 'Life, the Universe and Everything'} response = test_client.post('/users', data=json.dumps(data_in), content_type='application/json') assert response.status_code == 400 data_out = json.loads(response.data) assert len(data_out['errors']) == 2 http://www.zdrojak.cz/clanky/json-schema-praxi/ 13.3.2015 11:36:41 JSON Schema v praxi - Zdroják Stránka 5 Jedna poučka praví, že HTTP kódy 5xx indikují selhání na straně tvůrce serveru, zatímco 4xx mají hlásit problém na straně uživatele. Budeme se jí tedy držet a vracíme chybovou odpověď s HTTP kódem 400. Jestliže selže něco jiného, tak spoléháme na skutečnost, že Flask umí z nezachycených výjimek automaticky vytvořit „pětistovku“ (byť v tomto základním nastavení se bude jednat o HTML stránku a ne o JSON – úprava tohoto chování je ovšem nad rámec ukázky). Zkusíme-li teď poslat stejná data s curl , odpoví nám server takto: $ curl -X POST -H "Content-Type: application/json" -d '{"id": 2,"name":"Honza","age":"Life, the Universe and Everything"}' 'http:// localhost:5000/users' { "errors": [ "Additional properties are not allowed (u'id' was unexpected)", "u'Life, the Universe and Everything' is not of type u'integer'" ] } Jak vidíme, „zadarmo“ jsme získali mocný validační mechanismus a k němu navíc docela ucházející výchozí chybové hlášky, z nichž si dokáže uživatel našeho API snadno a rychle domyslet, co dělá špatně. Kdyby to viděl Horst Fuchs (https://cs.wikipedia.org/wiki/Horst_Fuchs), přihodil by na stůl ještě skutečnost, že nyní máme validaci založenou na jednoduchém textovém souboru (vhodné např. k verzování), jehož obsah je v nezávislém, standardním formátu (řádka knihoven pro různé jazyky). Můžeme tak bez problémů tuto „specifikaci“ pro data přijímaná naším API sdílet – a když na to přijde, třeba rovnou na adrese /users/schema . Snadno lze na takové schéma potom odkázat z dokumentace, nebo přímo z odpovědí v API, např. přes hlavičky (http://tools.ietf.org/html/rfc5988). Kód k této fázi naleznete zde (https://github.com/honzajavorek/zdrojak-json-schema/commit/ccfc4fd765d471dfaacd44d6070ca4987acda334) (odkaz přímo na commit). Vnořená schémata Představte si, že chceme k uživateli ukládat adresu. Mohli bychom ji samozřejmě snadno dopsat do našeho schéma v user.json jako vnořený objekt, ale brzy bychom narazili na to, že adresu ukládáme např. i u firem a musíme její definici udržovat na více místech zároveň. Zkusíme si tedy ukázat, jak bychom mohli ukládat adresu do zvláštního schéma a z popisu uživatele na ni jen odkázat. Nejdříve připravíme schémata: { "$schema": "http://json-schema.org/schema#", "title": "User", "type": "object", "properties": { "age": {"type": "integer"}, "name": {"type": "string"}, "address": {"$ref": "address.json#"} }, "required": ["name"], "additionalProperties": false } Jak je vidět, odkazujeme na soubor address.json . Pojďme jej vytvořit: http://www.zdrojak.cz/clanky/json-schema-praxi/ 13.3.2015 11:36:41 JSON Schema v praxi - Zdroják Stránka 6 { "$schema": "http://json-schema.org/schema#", "title": "Address", "type": "object", "properties": { "street": {"type": "string"}, "number": {"type": "integer"}, "city": {"type": "string"}, "country": {"type": "string"}, "zip_code": {"type": "integer"} }, "required": ["country", "city"], "additionalProperties": false } Aby knihovna jsonschema při validaci dokázala odkazovaný soubor najít, musíme rozšířit naši funkci validate a přidat do ní tzv. RefResolver , jenž nasměrujeme do správné složky: from jsonschema import RefResolver def validate(schema_filename, data): with open(schema_filename) as f: schema = json.load(f) # cteme JSON Schema primo ze souboru Validator.check_schema(schema) # zkontroluje schema nebo vyhodi vyjimku base_uri = 'file://' + os.path.dirname(schema_filename) + '/' resolver = RefResolver(base_uri, schema) validator = Validator(schema, resolver=resolver) return validator.iter_errors(data) # vraci chyby jednu po druhe A to je vše. Nyní nám zbývá už jen validaci adres odzkoušet v testech: def test_send_address(test_client): data_in = {'name': 'Honza', 'address': {'country': 'Czech Republic', 'city': 'Krno'}} response = test_client.post('/users', data=json.dumps(data_in), content_type='application/json') assert response.status_code == 201 data_out = json.loads(response.data) assert data_out['id'] assert data_out['name'] == data_in['name'] assert data_out['address'] == data_in['address'] def test_invalid_address(test_client): data_in = {'name': 'Honza', 'address': {'city': 'Krno', 'number': '11', 'mayor': 'Rumun'}} response = test_client.post('/users', data=json.dumps(data_in), content_type='application/json') assert response.status_code == 400 data_out = json.loads(response.data) assert len(data_out['errors']) == 3 http://www.zdrojak.cz/clanky/json-schema-praxi/ 13.3.2015 11:36:41 JSON Schema v praxi - Zdroják Stránka 7 Hotovo. Finální kód naší malé aplikace je kompletně k dispozici v repozitáři na GitHubu (https://github.com/honzajavorek/zdrojak-json-schema). Celá validace se pak na základě dalších vylepšení dá slušně automatizovat – do de facto deklarativní roviny ji přesunulo rozšíření (https://github.com/mattupstate/flaskjsonschema#flask-jsonschema)Flask-JsonSchema (https://github.com/mattupstate/flask-jsonschema): @app.route('/users', methods=['POST']) @jsonschema.validate('user', 'create') def users(): ... Závěrem Cílem článku nebylo vytvořit dokonalou a neprůstřelnou aplikaci nebo hlásat jediný správný způsob, jak něco dělat. Také Python byl vybrán spíše pro názornost – stejný příklad byste mohli sestrojit v Javě nebo JavaScriptu. Mým záměrem bylo představit schéma jako obecnou technologii, která je u nás bohužel podužívaná a přitom by mohla vyřešit mnoho zbytečných problémů na obou stranách „API barikády“. Chtěl jsem prakticky ukázat, že JSON Schema není žádnou pochybnou, nostalgickou iniciativou XML nadšenců, které někdo donutil přejít na JSON. A v neposlední řadě jsem si přál vyvolat ve vás zájem, jenž by způsobil, že si otevřete návod k JSON Schema (https:// spacetelescope.github.io/understanding-json-schema/) a podíváte se, co všechno dokáže validovat (https://spacetelescope.github.io/understanding-jsonschema/reference/regular_expressions.html) a jaké má pokročilé funkce (https://spacetelescope.github.io/understanding-json-schema/reference/combining.html) , nebo že se zamyslíte nad tím, jaké možnosti se vám užitím schématu otevírají. Snad se mi to povedlo. Honza Javorek (http://www.zdrojak.cz/autori/honza-javorek/) Přispívám v Apiary (http://apiary.io) k tomu, aby se stal API Blueprint (http://apiblueprint.org/) nejlepším formátem pro popis webových API. Pomáhám v ČR propagovat jazyk Python (http://honzajavorek.cz/blog/proc-python.html). Mám blog Javorové lístky (http://honzajavorek.cz/blog/), kam píšu, když zrovna nepíšu pro Zdroják. (http://honzajavorek.cz) T (http://www.twitter.com/honzajavorek) Věděli jste, že nám můžete zasílat zprávičky (http://www.zdrojak.cz/zpravicky-new)? (Jen pro přihlášené.) Zdroj: http://www.zdrojak.cz/?p=12037 http://www.zdrojak.cz/clanky/json-schema-praxi/ 13.3.2015 11:36:41
Podobné dokumenty
Generování syntaktických analyzátoru
nedělá to nic zvláštnı́ho, jen využı́vá toho, že lex defaultně čte ze standardnı́ho
vstupu.
pokud bychom to chtěli vylepšit. . .
Webová aplikace pro sdílení asistentů pro aplikaci
Aplikace DevAssistant, která pomáhá automatizovat opakující se činnost programátorů jinou než programování samotné, umožňuje uživatelům vytvářet
vlastní asistenty, tedy něco jako recepty na jednotl...
FV_Příbalová informace
reakce (PCR). Metoda je založena na amplifikaci cílové sekvence a její detekci pomocí alelově specifického signálu fluorescenčně
značených sond. Cílovou sekvencí je jednonukleotidová záměna guaninu...
Návrh privátní IaaS cloudové platformy - Newt on-da-line
Cloudové IaaS platformy nemají jednotný postup instalace (2) (3) (4). Každá instalace je
jedinečná, liší se počtem fyzických serverů, možnostmi použité virtualizační platformy a
topologií sítě. Pro...
Obecné pokyny k řádným zásadám odměňování podle
č. 1093/2010, na které se tyto obecné pokyny vztahují, by s nimi měly být v souladu a začlenit
je do svých postupů (např. pozměněním právního rámce nebo dohledových postupů), včetně
případů, kdy js...