JSON Schema – Walidacja JSON-a

Json SCHEMA

Ponieważ JSON jako format danych zyskuje coraz większą popularność, pojawia się również coraz więcej rozszerzeń i bibliotek ułatwiających pracę z jego wykorzystaniem. Bardzo długo jedną z głównych bolączek tego formatu był brak standardów określających walidację poprawności strukturalnej dokumentu. Usługi wymieniały między sobą komunikaty korzystając z notacji JSON, jednak brakowało spójnego rozwiązania określającego format tych komunikatów. Odpowiedzią na ten problem jest projekt JSON Schema, który został już udostępniony do szerszych testów w wersji draft.

JSON Schema korzystając z notacji JSON, definiuje nowy format do walidacji, dokumentacji i kontroli interakcji dla wymienianych danych. Można to porównać do deklaracji swego rodzaju kontraktu zawieranego między usługami.

Koncepcja takiego formatu została zaczerpnięta z XML Schema (XSD), a co za tym idzie, widać bardzo wiele podobieństw między tymi standardami.

JSON Schema – Przykładowa specyfikacja

Załóżmy, że dla poniższego obiektu typu JSON chcemy napisać schemę walidującą jego poprawność.

Poniższa schema definiuje typy przesyłanych danych oraz oznacza je jako wymagane.

Prześledźmy teraz na przykładach, jak mógłby wyglądać bardziej rozbudowany scenariusz takiej walidacji.

Standard JSON Schema

Jak w nauce każdego innego języka, czy nowego formatu zacznijmy od możliwie prostego przykładu.

Najprostszy format, czyli Hello World dla JSON Schema

Poniższy pusty obiekt JSON to poprawna schema akceptująca dowolny poprawnie zbudowany komunikat typu JSON.

Komunikaty spełniające te założenia to np.

Typ przesyłanego obiektu

Oczywiście tak ograniczona schema na niewiele nam się przyda, dlatego wprowadzimy ograniczenie na typ przesyłanego obiektu.

Robimy to przy pomocy słowa kluczowego: type. Obsługiwane typy to między innymi: string, number, integer, object, array, boolean i null.

Powyższa schema przepuści tylko komunikaty składające się z ciągów znaków, dlatego wszystkie liczby i obiekty zostaną odrzucone.

Wiele dozwolonych typów

Istnieje również możliwość zdefiniowania kilku dozwolonych typów. Robimy to przy pomocy tablicy, jak w przykładzie poniżej.

Dla tej schemy dozwolone są liczby oraz ciągi znaków.

Deklaracja JSON Schema

Ponieważ każda schema jest jednocześnie obiektem typu JSON, dla ich prostszego odróżnienia dobrą praktyką jest jawna deklaracja schemy jako JSON Schema. Robimy to przy pomocy słowa kluczowego $schema oraz URI wskazującego obsługiwany typ formatu.

Deklaracja unikatowego identyfikatora

Nie jest to niezbędny element schemy, jednak staje się bardzo przydatny, szczególnie gdy chcemy korzystać z zagnieżdżonych obiektów (więcej o tym za chwilę).

Własność id wykorzystywana jest na dwa sposoby:

  • jako unikatowy identyfikator dla schemy;
  • oraz jako base URL, na podstawie którego ustalane są lokalne odwołania;

Metadata

Standard JSON Schema określa również zbiór słów kluczowych, przeznaczonych do opisu przesyłanego obiektu, są to:

  • title
    Zawierający krótki opis komunikatu. Pole musi być typu string.
  • description
    Dłuższy opis. To pole również musi być typu string.
  • default
    Pola zawiera domyślną wartość dla tego elementu.

Typ Enum

Istnieje również możliwość ograniczenie przesyłanych danych do konkretnego zbioru dozwolonych wartości. Robimy to przy pomocy słowa kluczowego: enum oraz tablicy tych wartości.

Powyższa schema będzie poprawna tylko dla dwóch wartości: red oraz blue.

Enum vs Type

Ciekawostką jest to, że walidacja odbywa się niezależnie dla ograniczeń z pola type i enum.

W związku z powyższym: dla tego przykładu, mimo iż pusty obiekt JSON jest zdefiniowany w sekcji enum, taka wartość zostanie odrzucona, ponieważ nie spełnia ograniczenia na typ.

Łączenie schematów

Standard JSON Schema daje również możliwość łączenia ze sobą kilku różnych ograniczeń.

W powyższym przykładzie poprawne będą komunikaty składające się z ciągu znaków o maksymalnej długości 3 lub cyfry z zakresu od 1 do 3.

Łączenie ograniczeń w ten sposób daje ogromne możliwości oraz swobodę korzystania. Szczególnie jeżeli pod uwagę weźmiemy wszystkie pozostałe dostępne słowa kluczowe:

  • allOf
    Komunikat musi być zgodny ze wszystkimi zdefiniowanymi ograniczeniami.
  • anyOf
    Wystarczy, że komunikat będzie zgodny przynajmniej z jednym zdefiniowanym schematem.
  • oneOf
    Przesyłana wiadomość musi pasować dokładnie do jednego schematu.
  • not
    Zaprzeczenie, czyli wiadomość nie może pasować do zdefiniowanego wzorca.

Złożona struktura schematów JSON Schema

Dla bardziej złożonych schematów, szczególnie zawierających zagnieżdżone obiekty, nieoceniona staje się odpowiednio ułożona struktura danych oraz możliwość ponownego użycia wcześniej zdefiniowanych obiektów.

Typ obiektowy

Zacznijmy od wprowadzenia nowego typu: obiektowego. Złożone obiekty definiujemy przez typ: object oraz podanie jego własności.

W tym przykładzie zdefiniowaliśmy osobę składającą się z 2 wymaganych własności: imienia oraz wieku.

Poniżej przykładowy komunikat spełniający te założenia.

Ponieważ zadeklarowaliśmy zmienną: additionalProperties jako false, żadna dodatkowa własność obiektu nie jest dozwolona. Ta zmienna jest opcjonalna i domyślnie przyjmuje wartość true.

Dodatkowe walidacje zależności

Standard daje również możliwość wprowadzenia dodatkowych opcjonalnych walidacji, które zostaną sprawdzone tylko, jeżeli jakieś pole zostało podane w komunikacie.

Zależności właściwości

Wracając do naszego przykładu, rozszerzmy naszą osobę o właściwość przechowującą pracę. Co więcej, jeżeli praca jest zdefiniowana, to wymagamy również podania pensji.

W tym celu dodajemy nową właściwość: work z ograniczeniem na typ oraz wprowadzamy nową sekcję: dependencies, gdzie klucz jest właściwością, a wartość – tablicą właściwości, które będą wymagane, jeżeli ta pierwsza właściwość będzie podana.

Takie walidacje zależności są jednak dość ograniczone, dlatego trochę rozszerzymy je w kolejnym kroku.

Zależności schematu

Załóżmy, że chcemy wprowadzić kolejne ograniczenia:

  • pensja musi być podana jako dodatnia liczba całkowita:

Ponowne wykorzystanie definicji

Dobrze ułożona struktura złożonych obiektów zawiera referencje do często wykorzystywanych definicji. Zamiast za każdym razem powtarzać definicje obiektów, ich deklarację można przenieść do sekcji: definitions i tylko odwoływać się do nich, przy pomocy specjalnej referencji (w formacie: JSON Pointer).

Zobaczmy to na przykładzie. Stworzymy nową schemę dla obiektu reprezentującego rodzinę, wykorzystując wcześniej zdefiniowany obiekt osoby.

Zrobimy to w kilku krokach:

  1. Przenieśmy definicję osoby do sekcji: definitions.
  2. Zdefiniujmy właściwości obiektu rodzina. W naszym przykładzie będzie: ojciec, mama oraz dzieci.
    • Własność father oraz mother zawierają referencję do zdefiniowanej wcześniej osoby.
    • Własność children została zdefiniowana jako tablica obiektów typu person, o co najmniej jednym elemencie oraz wymogiem unikatowości elementów.
  3. Wszystkie trzy zdefiniowane pola zostały również oznaczone jako wymagane.
Dla tak zdefiniowane obiektu rodziny poniższy obiekt będzie poprawny.
Referencje do definicji można łączyć z zagnieżdżonymi definicjami i stosować je tylko tam, gdzie jest to potrzebne, czyli np. tam, gdzie dana definicja zostanie wykorzystana więcej niż raz.

Rozszerzanie definicji

Prawdziwa moc referencji jest widoczna dopiero, jeżeli zestawimy je z możliwością łączenia walidacji z różnych schematów.

Dzięki tym mechanizmom możemy wydzielić bardziej generyczne właściwości do jednego obiektu i tylko uszczegóławiać je w razie potrzeby.

W naszym przypadku wprowadzimy dla ojca dodatkowe ograniczenie. Załóżmy, że żeby zostać pełnoprawną głową rodziny, trzeba mieć ukończone 18 lat.

JSON schema validator

Umiemy już zdefiniować odpowiednie ograniczenia, jakie chcemy, żeby spełniał nasz komunikat. Teraz czas na przygotowanie odpowiedniego mechanizmu, który będzie w stanie zweryfikować poprawność komunikatu względem tego kontaktu.

Wykorzystamy do tego bibliotekę json-schema-validatorcom.github.fge.

W pierwszym kroku odczytujemy schemę z pliku, a następnie weryfikujemy nasz obiekt przy pomocy funkcji: schema.validate.

Prawda, że proste? 🙂

W razie czego kod wszystkich przykładów zawsze dostępny jest w repozytorium git.

W przypadku niespełnienia wszystkich wymagań możemy odczytać dokładny komunikat o niepowodzeniu.

Podobny efekt można również uzyskać, korzystać z narzędzi online, np. sonschemavalidator.net.

JSON Schema Validator

JSON Schema Validator

JSON schema generator

Istnieje możliwość wygenerowania schemy na podstawie istniejącego już obiektu JSON.

Trzeba jednak liczyć się z pewnymi ograniczeniami tego podejścia. Wygenerowana w ten sposób JSON Schema będzie miała bardzo uproszczoną, a nawet trochę niechlujną postać.

Na podstawie samego komunikatu można wywnioskować jedynie jakie pola są w obiekcie oraz sprawdzić ich typ. Wygenerowana schema nie będzie zawierała żadnych dodatkowych ograniczeń, zależności, czy referencji.

Mimo tych wszystkich ograniczeń może to być bardzo dobry punkt wyjścia do późniejszych ręcznych prac nad własną schemą.

Ja w tym celu korzystałem z narzędzia online: jsonschema.net.

JSON Schema Generator

JSON Schema Generator

Podsumowanie JSON Schema

Mimo ciągle trwających prac, specyfikacja JSON Schema sama w sobie już teraz sprawia wrażenie bardzo dopracowanego rozwiązania. Niestety brakuje jeszcze wsparcia ze strony zewnętrznych narzędzi i bibliotek, ale to prawdopodobnie przyjdzie z czasem.

Rozwiązanie walidacji JSON-a w tej formie jest wyjątkowo funkcjonalne, a przy tym podobnie jak sam JSON, jest również bardzo lekkie, nie wprowadzające dodatkowych narzutów wydajnościowych.

Dzięki tej technologii stosunkowo łatwo można wprowadzić w projekcie dodatkowe testy walidujące, to co zwracamy z naszych usług restowych. Ten niewielki wysiłek w przyszłości może zabezpieczyć nas przed dziwnymi błędami po stronie klienta.

Linki

Programista – Pytania rekrutacyjne

Lista pytań rekrutacyjnych, które pozwolą przygotować Ci się na rozmowę kwalifikacyjną.

No comments
Share:

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *