JSON Schema. Быть или не быть?

Архитектура: искусство делать излишнее необходимым.

Фредерик Кислер

Ни для кого давно уже не секрет, что для любого web-сервиса на протоколе SOAP с сообщениями в формате XML верным и проверенным временем решением является предварительная разработка XML Schema (xsd-схемы), описывающей типы данных и структуру XML сообщений. При этом подходе у разработчиков существует явное преимущество: у них есть строгие стандартизированные правила по структуре сообщений, которые заданы в схеме, число правил конечно, и они позволяют автоматизировать проверку любого нового сообщения в формате XML.

Но также известно, что язык XML потеснился языком разметки JSON (JavaScript Object Notation) в виду его большей тяжеловесности, а также в виду распространения архитектурного стиля REST (REpresentational State Transfer) разработки программного обеспечения для распределенных систем. Хотя сам REST-стиль не требует использования JSON (он вообще, можно сказать, ничего не требует, а «рекомендует»), но как показывает практика, чаще при разработке REST API все-таки используется JSON для описания тела сообщений.

Так вот, практика разработки REST API с JSON–сообщениями прочно вошла в жизнь ИТ в России (и не только у нас), хотя опыт при этом по описанию структуры сообщений в виде XML Schema, значительно упростивший жизнь разработчиков web-служб в свое время, упорно игнорируется в случае c JSON–сообщениями. Но не всеми, что не может не радовать.

Когда разработчики, знакомые с XML Schema, столкнулись с необходимостью каждый раз заново изобретать велосипед с разбором документов и переизобретать логику валидации, сформировался аналогичный драфт JSON Schema. Он доступен по адресу json-schema.org, а также ряд документов по истории изменений и примеров использования. И несмотря на то что он публикуется в статусе «draft», его давно уже поддерживают все популярные платформы разработки и библиотеки на разных языках.

Сама JSON Schema предоставляет меньше возможностей по структуризации сообщений, чем XML Schema. То, что легко можно описать через XML Schema, не всегда будет тривиальной задачей повторить с помощью JSON Schema, если вообще это будет возможно. Но здесь же я бы данный факт стала рассматривать и как преимущество. Почему?

Известно, что чем проще и линейнее алгоритм работы системы, тем она и надежнее, что чем проще структура документа, тем легче он для восприятия и т.д.
Не могу удержаться, чтобы не процитировать: «Всё гениальное просто, и всё простое гениально». И если не удается с помощью схемы описать сложную структуру документа и множество допустимых вариантов, то, возможно, стоит посмотреть в сторону упрощения самой структуры и логики формирования этого документа?

Предисловие

Итак, о чем же эта статья?
Я бы хотела привлечь больше внимания к преимуществам описания передаваемых JSON сообщений схемой JSON Schema. Несмотря на то, что «на входе» разработка REST API без какой-либо JSON-схемы всегда проще и быстрее, но с ростом системы, ее отсутствие так или иначе приводит к удорожанию сопровождения и поддержки системы. Также любая предварительная проработка структуры сообщений способствует более качественной организации обмена сообщениями, без лишнего дублирования при обмене данными и общими правилами их обработки.

Также в целях распространения информации в русскоязычном сообществе о возможностях JSON Schema и правилах работы с ней я поделюсь своим некоторым опытом на конкретных примерах в рамках данной статьи.

Постановка задачи

Перед тем как приступить к изучению JSON и JSON Schema, опишу задачу, на которой мы будем рассматривать все примеры далее.
Рассмотрим ролевую модель управления в организации. Предполагаем, что справочную информацию по существующей ролевой модели мы должны будем передавать в зависимые системы в сообщениях в формате JSON посредством вызова REST-сервиса.

Описание задачи:
В организации есть сотрудники, им часто приходится работать одновременно в нескольких системах. При этом уровень доступа (полномочий) к тем или иным компонентам системы (ресурсам) для разных сотрудников в зависимости от их роли в организации может отличаться, и должен контролироваться при авторизации пользователя в системе.

Например, бухгалтер (роль) будет иметь доступ на чтение и редактирование (операции/полномочии) к расчетным листкам (ресурс) по заработной плате всех сотрудников, а у аналитика (роль), к примеру, будет доступ на чтение (операция/полномочии) только по своему расчетному листку (ресурс).

Необходимо спроектировать и описать ролевую модель управления в организации. Доступные роли, набор возможных полномочий и ресурсов в системе необходимо передавать другим системам по запросу.

Рисунок 1. Представление компонентов ролевой модели

Способы описания и реализации ролевой модели могут отличаться, но независимо от реализации чаще всего в ролевой модели в таком случае мы можем выделить следующие базовые составляющие:

  1. Роль (например, менеджер, бухгалтер и т.д.).
  2. Ресурс (например, документ, объект недвижимости и т.д.).
  3. Операция/полномочии (например, прочесть, распечатать, создать и т.д.).

При описании ролевого доступа (как один из возможных вариантов) прибегают к созданию дискретной матрицы доступа на основе выделенных сущностей, например:
Таблица 1. Дискретная матрица доступов.

Ресурс: documents Ресурс: objects
Роль: manager read, print read, create
Роль: accountant read, create read

Далее в статье мы ознакомимся сначала с теоретической составляющей текстового формата обмена данными JSON и правилами их структурирования с помощью JSON Schema, а в качестве примеров буду приводить описание сущностей-справочников для ролей, ресурсов и операций на языке JSON и их JSON–схем в рамках нашей поставленной задачи.

JavaScript Object Notation (JSON)

JSON (англ. JavaScript Object Notation) — текстовый формат обмена данными, основанный на JavaScript.

Теория

Язык разметки JSON задает ограниченный набор типов данных. Для пары {“ключ”: значение} для «ключа» всегда используют тип string, для «значения» применимы типы: string, number, object (тип JSON), array, boolean (true или false) и null.


Рисунок 2. Типы данных JSON

На рисунке приведены базовые типы и примеры их использования. Достаточно просто все, на мой взгляд.
Синтаксис JSON является подмножеством синтаксиса JavaScript, где:

  1. Данные записываются в виде пар {“ключ”: значение}.
  2. Данные разделяются запятыми.
  3. В фигурных скобках записываются объекты.
  4. В квадратных скобках записываются массивы.
  5. Наименования «ключей» регистрозависимы.


Рисунок 3. Синтаксис JSON

Практика

Рассмотрим пример справочника ролей, который мы будем передавать в сервисе:

Рисунок 4. Описание справочника ролей в формате json

Из примера видно, что даже несмотря на столь небольшое число базовых типов, при их комбинации мы можем создавать более сложные структуры сообщений при необходимости. Здесь, в частности, я описываю справочник ролей через объект массивов, содержащих другие объекты (на рисунке 4 выделены двумя прямоугольниками).
В табличном виде с помощью средств визуализации json-сообщений справочник может быть представлен следующим образом:


Рисунок 5. Визуализации справочника ролей в формате JSON

Справочник, условно говоря, представляет собой 3 «таблицы» для задания ролей в группе администраторов, бухгалтеров и рабочих. Состав «атрибутов» может быть расширен, при необходимости.
Визуальное представление, на мой взгляд, упрощает восприятие текстового описания. Аналогичную структуру зададим и для двух других справочников. Приведу ниже пример только табличного представления для справочника полномочий (операций) и ресурсов.


Рисунок 6. Визуализации справочника полномочий в формате JSON


Рисунок 7. Визуализации справочника ресурсов в формате JSON

Исходные сообщения в текстовом формате JSON для справочника ролей, ресурсов и полномочий можно скачать/просмотреть по ссылке.
Теперь перейдем к самому интересному: к изучению JSON Schema и созданию схемы под наши справочники!

JSON Schema

Теория

Поскольку схема json написана в формате JSON, она поддерживает все типы JSON плюс дополнение: тип integer, который является подтипом типа number. Сама схема является JSON-объектом и предназначена для описания данных в формате JSON. Ниже приводится схема типов данных, используемых при создании самой схемы:


Рисунок 8. Типы данных JSON Schema

Как видно из рисунка, для схемы используются все те же типы данных, а также все те же принципы синтаксиса, что и для обычного документа JSON, приведенные на рисунке 3.

Теперь рассмотрим самое важное — правила, используемые в схеме для задания ограничений и структурирования JSON-сообщений.

JSON Schema позволяет:

  1. Ограничить тип данных для элементов документа JSON.
  2. В зависимости от типа проверяемых данных, также могут быть применимы дополнительные правила — «keywords», начиная с корня схемы документа и спускаясь к их дочерним элементам.

Некоторые «ключевые слова» являются чисто описательными, как например: «title», «description» и др., которые просто описывают предназначение схемы. Другие предназначены для идентификации документа: «$schema». Это ключевое слово используется для указания желаемой версии схемы. Значение этого ключевого слова должно быть строкой, представляющей URI, например: "$schema": «json-schema.org/draft-04/schema#».

Здесь очень важно обратить внимание, что не все версии могут поддерживаться вашим инструментом работы со схемой. Но 4-й драфт поддерживают практически все. О последних изменениях (JSON Schema 2019-09 Release Notes) для разных версий можно познакомиться по ссылке json-schema.org/draft/2019-09/release-notes.html.
Остальные ключевые слова используются непосредственно для проверки документа JSON. Их мы сейчас и рассмотрим.
Таблица 2. Анализ структуры JSON Schema. Ключевые слова и их примеры использования.

Тип Keyword(s) Пример/описание
«Keywords» для описания схемы "$schema"
"$schema": http://json-schema.org/draft-04/schema#

Используется для задания версии драфта схемы.

"$id"
"$id": "http://archi-blair.com/schemas/RolesDictionaryDef.json#"

Используется для указания уникального идентификатора документа или его подсхем.

"title"
"description"
"examples"
"comment"
{
"title": "JSON schema for dictionary",
"description": "Справочники ролей",
"examples": ["user", "manager"],
"comment": "не поленитесь прочесть статью до конца))"
}

Общие «Validation keywords», независимые от типа данных элемента "enum"
{"enum": [ "administrator", "superuser" ]}

Выполняется проверка на совпадение с хотя бы 1 значением.

"const"
{"const": "user" }

Выполняется проверка на точное соответствие заданному значению.

"type"
{"type": ["number", "string", "null", "boolean"]}
{"type": "array"}

Указывает тип данных, который будет использоваться схемой. Это ключевое слово не является обязательным, и значением ключевого слова может быть строка, представляющая допустимый тип данных, или массив строк, представляющих допустимые типы данных.

Keywords, зависимые от того типа данных, с которым они используются "type": "string"

minLength
maxLength
pattern
contentEncoding
contentMediaType

{
"type": "string",
"minLength": 3,
"maxLength": 10,
"pattern": "^test\/[a-z-]+$",
"contentEncoding": "base64",
"contentMediaType": "application/json"
}

Выполняется проверка на соответствие символьного выражения заданным параметрам.

"type": "number" или "type": "integer"

minimum
exclusiveMinimum
maximum
exclusiveMaximum
multipleOf

{
  "type": "number",
  "minimum": 1.5,
   "exclusiveMinimum": true,
   "maximum": 12.3,
   "exclusiveMaximum": true,
   "multipleOf": 0.5
}

Выполняется проверка на соответствие числового выражения заданным параметрам.

"type": "object"

properties
required
dependencies
minProperties
maxProperties
propertyNames
patternProperties
additionalProperties


"employees": {
 "description": "Сотрудники",
 "type": "array",
  "uniqueItems": true,
   "items": {
     "type": "object",
     "properties": {
           "name": {
               "type": "string",
               "enum": ["employee"],
               "enumNames": ["Сотрудник"]
	    },
            "enabled": {
                "type": "boolean",
                "default": true
             }
    },
 "additionalProperties": false
 }
}

Объект проверяется как по наименованию ключа, так и по ограничениям на значение (в примере приведены не все ключевые слова).

"type": "array"

minItems
maxItems
uniqueItems
contains
items
additionalItems


"employees": {
 "description": "Сотрудники",
 "type": "array",
 "uniqueItems": true,
 "items": {
    "type": "object",
    "properties": {
          "name": {
               "type": "string",
               "enum": ["employee"],
               "enumNames": ["Сотрудник"]
            },
          "enabled": {
                "type": "boolean",
                "default": true
            }
     },
 "additionalProperties": false
 }
}

Массив проверяется как по наименованию ключа, так и по ограничениям на значение (в примере приведены не все ключевые слова).

"type": "boolean"
Ключевые слова не используются
{"type": "boolean"}

Тип boolean используется для проверки только логических значений (true или false).

"type": "null"
Ключевые слова не используются
{"type": "null"}

Тип null используется для проверки «отсутствия» значения.

"type": "____"
"format": "____"
{
  "type": "string",
  "format": "date"
}

Ключевое слово format выполняет семантическую проверку данных. Поведение ключевого слова зависит от типа данных, т.е. одно и то же имя формата для строки ведет себя по-иному, чем для числа или отсутствует, поскольку не все типы данных должны реализовывать формат, и обычно разные типы данных имеют разные форматы.

"type": "____"
"default": "____"
{
"enabled": {
	"type": "boolean",
	"default": true
}	

Значение по умолчанию для элемента.
Это ключевое слово не является обязательным, и значение этого ключевого слова может быть любым.

Ключевые слова для наложения условий на проверки "not"
"if-then-else"
{
  "not": {
    "type": "string"
  }
}

Ключевые слова поддерживаются любым типом, являются необязательными.

Ключевые слова, объединяющие проверки для разных частей схемы "anyOf"
"oneOf"
"allOf"
{
  "type": "string",
  "anyOf": [
    {"const": "user"},
    {"const": "manager" }
  ]
}

Ключевые слова поддерживаются любым типом, являются необязательными.

Ключевые слова, обеспечивающие ссылочность и связность схем "$id" и "$ref" Схема RolesDictionaryDef.json:

{
"$id": http://archi-blair.com/schemas/RolesDictionaryDef.json#
}

Ссылаемся на схему 1 с помощью $ref (приведена часть схемы со ссылкой):


"items": {
"type": "object",
"minLength": 1,
"properties": {
  "name": {
  "description": "Наименование группы словарей",
  "type": "string"
   },
"description": {
  "description": "Описание словарей",
  "type": "string"
 },
"dictionaryGroup": {
  "$ref": "RolesDictionaryDef.json#/definitions/roles"
    }
},
"additionalProperties": false
}

Мы рассматриваем 2 разных схемы JSON. Чтобы повторно использовать схему, мы делаем ссылку на нее с помощью ключевого слова $ref в другой схеме.

"$ref" и "definitions" Через "$ref": "#/definitions/roles" ссылаемся на часть текущего документа, описанного после ключа «definitions»:

{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Справочники ролей",
"type": "object",
"properties": {
"dictionaries": {
   "description": "Справочники",
   "type": "array",
   "maxItems": 1,
   "items": {
      "type": "object",
      "minLength": 1,
      "properties": {
               "name": {
                  "description": "Наименование словаря",
                  "type": "string",
                  "enum": [
                     "Roles Dictionary"
                     ]
                 }
       "dictionaryGroups": {
             "$ref": "#/definitions/roles",
             "description": "Справочные данные"
          }
      },
 "additionalProperties": false
   }
  }
 },
 "additionalProperties": false,
     "definitions": {
          "roles": {
             "description": "Роли",
             "type": "object",
             "properties": {
             "administrators": {
             "description": "Администраторы",
             "type": "array",
             "uniqueItems": true,
             "items": {
             "type": "object",
             "properties": {
                   "name": {
                      "type": "string",
                      "enum": [
                         "administrator", 
                         "superuser"
                          ],
                        "enumNames": [
                          "Администратор", 
                          "Пользователь-админ с ограниченным доступом"
                           ]
                         },
                    "enabled": {
                    "type": "boolean",
                    "default": true
                  }
              },
    "additionalProperties": false
      }
    }			
   },
  "additionalProperties": false
  }
 },
 "$id": "http://archi-blair.com/schemas/RolesDictionaryDef.json#"
}

Для ссылок на проверки, относящихся только к текущему документу схемы. Реализуется с помощью ключевого слова $ref и ключевого слова definitions.

"$ref"
Абсолютные и относительные указатели.
"$ref": " #/definitions/roles"
"$ref": "RolesDictionaryDef.json#/definitions/roles"

Абсолютный указатель используется для поиска, начиная с корня документа.
Относительный используется для поиска значений, начиная с текущего местоположения.

Мы рассмотрели ключевые слова схемы JSON, позволяющие описать нам будущую структуру наших сообщений в формате JSON.
Здесь вы можете найти больше примеров использования ключевых слов.

Практика

При рассмотрении примеров завершенных JSON-схем мы поступим аналогично примерам работы с самими сообщениями в формате JSON. Т.е. мы будем использовать визуальное представление в древовидном и табличном виде для наших схем справочников ролей, ресурсов и полномочий (операций), а с текстом схем предлагаю ознакомиться заинтересовавшихся читателей самостоятельно git.

Ниже приводится схема для справочника ролей.

Рисунок 9. Пример JSON Schema для справочника ролей

Как мы видим на рисунке, схема представляет собой JSON-объект и описывает наше сообщение для передачи справочника ролей в формате JSON, которое приводилось на рисунке 4. На текущем примере представлено как с помощью JSON схемы может быть описан объект массивов, состоящий из объектов.

Схемы двух других справочников (полномочий и ресурсов) по своей структуре идентичны со схемой для справочника ролей, поэтому не буду их здесь приводить, а приведу схему, объединяющую в себе все 3 справочника.
К сожалению, схема всего справочника при раскрытии не поместится на экране, поэтому рассмотрим ее часть.


Рисунок 10. Пример JSON Schema справочника, объединяющего в себе справочник ролей, полномочий и ресурсов

На рисунке мы видим, что часть объектов массива справочников подключена с использованием ключевого слова «anyOf».
Также, возможно, нагляднее будет табличное представление справочника.
Рассмотрим еще одну важную особенность нашей схемы:


Рисунок 11. Пример JSON Schema справочника, объединяющего в себе справочник ролей, полномочий и ресурсов в табличном представлении

Из рисунка мы видим, что объединяющий справочник не дублирует в себе код из ранее разработанных справочников ролей, полномочий и ресурсов, а использует ключевое слово "$ref".

Справочники, рассматриваемые в примерах, находятся в одной директории, но, при необходимости, это правило можно не соблюдать, а размещать в разных директориях, указав корректно путь к ним при подключении. Данная возможность очень полезна, так как позволяет переиспользовать ранее созданные схемы, лишь подключая их в нужные структуры.

На этом мой обзор JSON и JSON Schema я завершаю. Надеюсь, что приведенный здесь материал и рассмотренные примеры, окажутся полезными при изучении возможностей JSON Sсhema.

Вместо заключения

Думаю, пора подводить итоги.
Так что же применение JSON Schema в итоге нам может дать?

  1. Может упростить жизнь разработчикам и улучшить код по валидации JSON -сообщений.
    Иными словами, это упрощение поддержки и интеграции ПО.
  2. Позволит разрабатывать сервисы, прорабатывая форматы и состав данных с «заделом» на будущее развитие системы.
  3. Применить проверку документов в документо-ориентированных, объектно-ориентированных БД.
  4. JSON-Schema может помочь сэкономить время на тестировании и документировании API.
  5. Упрощение поддержки обратной совместимости API.
  6. Позволит управлять потоками данных.

Каждый из нас сам решает, «Быть или не быть JSON Schema» в наших IT -проектах. Выше я привела список того, что я считаю ключевым преимуществом применения схем, и ради чего уже стоит задуматься о ее применении в проектах.

Возможно, читатели захотят помочь мне продолжить этот список?
Я буду признательна 🙂

Также приведу список ссылок, на мой взгляд, полезных для работы с JSON и JSON Schema,

  1. Официальный источник с примерами использования ключевых слов схемы.
  2. Источник с большим числом примеров использования ключевых слов схемы.
  3. Официальная страница стандарта (драфта).
  4. Список релизов (полезно для понимания динамики развития стандарта).
  5. Онлайн-валидатор с возможностью выбрать нужную версию драфта JSON-Schema.
  6. Открытый репозиторий JSON Schema

И ссылку на git-репозиторий, где можно ознакомиться с исходными файлами, приводимыми для ознакомления в данной статье: репозиторий с исходными файлами примеров.

Системный архитектор,
© Ирина Блажина

Специально для сайта ITWORLD.UZ. Новость взята с сайта Хабр