# JSON-schema validation

# Валидация с JSON-SCHEMA

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

# Краткое описание работы JSON-Schema


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

Работа JSON Schema основывается на простом принципе - описания форматов JSON-данных. Оно представляет собой JSON-объект, который содержит различные ключи, представляющие собой «схемы». Эти схемы определяют типы данных, создают ограничения на значения и устанавливают требования к присутствию определенных полей. Все это позволяет обеспечить строгую валидацию данных и гарантировать их стабильность.

Такая схема считается гибким и мощным инструментом, поэтому с помощью JSON Schema можно определить основные характеристики данных: обязательность поля, max и min допустимые значения, соответствует ли значение поля регулярному выражению и многое другое.

# Библиотека ajv (opens new window)


Для валидации мы будем рассматривать js-библиотеку ajv (opens new window). Эту библиотеку можно использовать для валидации наших данных на фронте(Vue, React, jQuery и др.) и на бэке (node.js).

# Установка:

npm install ajv

# Пример использования:

Рассмотрим простой пример подключения и использования библиотеки:

import Ajv from 'ajv'
const ajv = new Ajv()

const schema = {
  type: 'object',
  properties: {
    foo: {
      type: 'string',
    }
  },
  required: [ 'foo' ],
  additionalProperties: false,
}

const data = {
  foo: 'test',
}

const valid = ajv.validate(schema, data) // true
if ( !valid ) {
    console.log( ajv.errors )
}

В примере в самом начале импортируется библиотека и с помощью конструктора AJV создается ее инстанс. Далее объявляется константа schema - это объект, схема, в которой хранится описание структуры наших данных. В примере наши данные, которые мы будем валидировать, представлены в виде объекта data. Внутри схемы есть следующие параметры (название):

  • type (string) - тип наших данных, которые мы собираемся провалидировать. В нашем случае это объект;
  • properties (object) - объект, в котором находятся поля нашей формы. ключ - название поля, значение - объект, в котором описывается соответствующее поле. в примере у полей описывается их тип. также там можно указывать min и max значения (для чисел), формат и регулярные выражения (для строк) и другое;
  • required (String[]) - массив строк, в котором находятся названия обязательных полей нашей формы;
  • additionalProperties (boolean) - указывает могут ли в нашей форме быть значения, которые не указываются в текущей json-схеме.

Валидация осуществляется с помощью метода validate, который первым параметром принимает схему, а вторым наши данные. Метод возвращает true, если валидация прошла успешна, в обратном случае false. В примере в случае ошибки валидации в консоль выводится список ошибок.

# Обработка ошибок

При неудачной проверке валидации массив ошибок будет доступен из свойства errors Ajv инстанса (в примере выше ajv.errors). Описание основных полей интерфейса ошибки :

interface ErrorObject {
  keyword: string // keyword валидатора
  instancePath: string // указывает путь до поля, в котором произошла ошибка, внутри наших данных (в формате JSON-pointer, например: '/foo')
  schemaPath: string // указывает путь до keyword json-схемы (в формате JSON-pointer, например: '#/properties/foo/type')
  params: object // содержит информацию об ошибке: keyword валидатора и название поля
  message?: string // необязательное поле, сообщение об ошибке
}

Немного обновим нашу схему и данные из примера выше и посмотрим на реальный список ошибок. По умолчанию массив ошибок возвращает только первую ошибку, которая была найдена при валидации. Для отображения всех ошибок при объявлении инстанса ajv передадим параметр { allErrors: true }:

const ajv = new Ajv({ allErrors: true })

const schema = {
  type: 'object',
  properties: {
    foo: {
      type: 'string',
    },
    bar: {
      type: 'number',
    },
  },
  required: [ 'foo', 'bar' ],
  additionalProperties: false,
}

const data = {
  foo: 1,
  baz: 'test',
}

const valid = ajv.validate(schema, data) // false
if ( !valid ) {
    console.log( ajv.errors )
}

В консоли выведется следующий массив:

[
	{
		instancePath: "",
		keyword: "required",
		message: "must have required property 'bar'",
		params: {
			missingProperty: "bar",
		},
		schemaPath: '#/required',
	},
	{
		instancePath: "",
		keyword: "additionalProperties",
		message: "must NOT have additional properties",
		params: {
			additionalProperty: "baz",
		},
		schemaPath: '#/additionalProperties',
	},
	{
		instancePath: "/foo",
		keyword: "type",
		message: "must be string",
		params: {
			type: "string",
		},
		schemaPath: '#/properties/foo/type',
	},
]

Как и ожидалось, наша форма не прошла валидацию по 3 причинам:

  • Отсутствие обязательного поля bar;
  • лишнее поле baz;
  • несоответствие типа у поля foo .

Этот ответ можно использовать для отображения сообщения ошибки пользователю

# $data reference

Если у нас в форме есть значения, которые зависят от значений из этой же формы, мы можем задать нужные ограничения с помощью $data reference.

const ajv = new Ajv({ $data: true })

const schema = {
  properties: {
    smaller: {
      type: 'number',
      maximum: {$data: '1/larger'},
    },
    larger: {type: 'number'},
  },
}

const validData = {
  smaller: 5,
  larger: 7,
}

ajv.validate(schema, validData)  // true

Для начала необходимо передать в инстанс ajv параметр { $data: true } . Далее в схеме мы указываем следующее условие: поле smaller должно быть не больше поля larger . Для этого в параметрах поля указываем ограничение maximum со значением объекта, в котором ссылаемся на нужное поле. Значение поля $data должно указываться в формате JSON-pointer (opens new window). Соответственно '1/larger' - это не деление, а путь до поля larger внутри формы validData. Подробнее можно ознакомиться в документации (opens new window).

# Валидация формата поля

Одна из полезных функций библиотеки - поддержка проверки формата полей. Для использования форматов необходимо будет установить дополнительный плагин - ajv-formats (opens new window).

Этот плагин позволяет проверять поля наших форм на следующие форматы: date, date-time, uri, email, hostname, ipv4, regexp, uuid и другие. Для использования всех форматов необходимо импортировать функцию addFormats и после объявления инстанса вызвать ее и передать сам инстанс:

import addFormats from 'ajv-formats'

const ajv = new Ajv()
addFormats( ajv )

При желании можно передать инстансу определенные форматы. Для этого вторым аргументом вызывается массив строк с нужными форматами:

addFormats( ajv, [ 'date', 'email' ] )

Также существует метод, для создания собственного формата:

ajv.addFormat("byte", {
  type: "number",
  validate: (x) => x >= 0 && x <= 255 && x % 1 == 0,
})

# Другие полезные фичи библиотеки

Я привел всего несколько особенностей этой библиотеки, которых оказалось достаточно для решения моей задачи. Помимо описанного выше, ajv также:

  • поддерживает typescript;
  • позволяет асинхронно подгружать схемы. Можно хранить схемы валидации для всего проекта в таблице БД. На фронте через бэк в нужный момент мы можем получать определенную схему и делать валидацию. Такой механизм может быть полезным, так как бэк cможет валидировать данные с помощью аналогичной json-схемы, используя свои инструменты (на node.js можно использовать ajv), например JSON.net (opens new window) для .net;
  • позволяет описывать логику валидации с помощью нескольких json-схем, в которых описание полей может ссылаться друг на друга (https://ajv.js.org/guide/combining-schemas.html (opens new window));
  • предоставляет плагин ajv-errors (opens new window), который позволяет составлять описание для сообщений об ошибках;
  • предоставляет плагин ajv-i18n (opens new window) для поддержки интернационализации;
  • и другое.

# Другие инструменты валидации

Очевидно, что говоря об инструментах валидации во Vue, сравнивая валидацию с помощью JSON-схем и vuelidate (opens new window), например, то схема позволит максимально точно, гибко и наглядно описать нужную структуру данных. При этом на маленьких проектах валидация с JSON-схемой может оказаться излишней.

# Вывод


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

  • используют и передают друг другу самые различные модели и структуры данных;
  • для которых важна надежность в обмене данными;
  • где есть многочисленные ограничения формата данных

это будет хорошим решением, так как повысит надежность кода и предсказуемость форматов данных. С такими инструментами как ajv можно легко настроить и интегрировать в свой сервис валидацию JSON-схемой на любом языке программирования.