Статьи

Проверка данных с помощью JSON-схемы, часть 1

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
  “id”: 64209690,
  “name”: “Jane Smith”,
  “email”: “jane.smith@gmail.com”,
  “phone”: “07777 888 999”,
  “address”: {
    “street”: “Flat 1, 188 High Street Kensington”,
    “postcode”: “W8 5AA”,
    “city”: “London”,
    “country”: “United Kingdom”
  },
  “personal”: {
    “DOB”: “1982-08-16”,
    “age”: 33,
    “gender”: “female”
  },
  “connections”: [
    {
      “id”: “35434004285760”,
      “name”: “John Doe”,
      “connType”: “friend”,
      “since”: “2014-03-25”
    },
    {
      “id”: 13418315,
      “name”: “James Smith”,
      “connType”: “relative”,
      “relation”: “husband”,
      “since”: “2012-07-03”
    }
  ],
  “feeds”: {
    “news”: true,
    “sport”: true,
    “fashion”: false
  },
  “createdAt”: “2015-09-22T10:30:06.000Z”
}

Вопрос, с которым мы собираемся разобраться, заключается в том, как определить, является ли запись, подобная приведенной выше, действительной или нет.

Примеры очень полезны, но недостаточны при описании ваших требований к данным. На помощь приходит JSON-схема. Это одна из возможных схем, описывающих запись пользователя:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
{
  “$schema”: “http://json-schema.org/draft-04/schema#”,
  “id”: “http://mynet.com/schemas/user.json#”,
  “title”: “User”,
  “description”: “User profile with connections”,
  “type”: “object”,
  “properties”: {
    “id”: {
      “description”: “positive integer or string of digits”,
      “type”: [“string”, “integer”],
      “pattern”: “^[1-9][0-9]*$”,
      “minimum”: 1
    },
    “name”: { “type”: “string”, “maxLength”: 128 },
    “email”: { “type”: “string”, “format”: “email” },
    “phone”: { “type”: “string”, “pattern”: “^[0-9()\\-\\.\\s]+$” },
    “address”: {
      “type”: “object”,
      “additionalProperties”: { “type”: “string” },
      “maxProperties”: 6,
      “required”: [“street”, “postcode”, “city”, “country”]
    },
    “personal”: {
      “type”: “object”,
      “properties”: {
        “DOB”: { “type”: “string”, “format”: “date” },
        “age”: { “type”: “integer”, “minimum”: 13 },
        “gender”: { “enum”: [“female”, “male”] }
      }
      “required”: [“DOB”, “age”],
      “additionalProperties”: false
    },
    “connections”: {
      “type”: “array”,
      “maxItems”: 150,
      “items”: {
        “title”: “Connection”,
        “description”: “User connection schema”,
        “type”: “object”,
        “properties”: {
          “id”: {
            “type”: [“string”, “integer”],
            “pattern”: “^[1-9][0-9]*$”,
            “minimum”: 1
          },
          “name”: { “type”: “string”, “maxLength”: 128 },
          “since”: { “type”: “string”, “format”: “date” },
          “connType”: { “type”: “string” },
          “relation”: {},
          “close”: {}
        },
        “oneOf”: [
          {
            “properties”: {
              “connType”: { “enum”: [“relative”] },
              “relation”: { “type”: “string” }
            },
            “dependencies”: {
              “relation”: [“close”]
            }
          },
          {
            “properties”: {
              “connType”: { “enum”: [“friend”, “colleague”, “other”] },
              “relation”: { “not”: {} },
              “close”: { “not”: {} }
            }
          }
        ],
        “required”: [“id”, “name”, “since”, “connType”],
        “additionalProperties”: false
      }
    },
    “feeds”: {
      “title”: “feeds”,
      “description”: “Feeds user subscribes to”,
      “type”: “object”,
      “patternProperties”: {
        “^[A-Za-z]+$”: { “type”: “boolean” }
      },
      “additionalProperties”: false
    },
    “createdAt”: { “type”: “string”, “format”: “date-time” }
  }
}

Посмотрите на схему выше и запись пользователя, которую она описывает (которая действительна в соответствии с этой схемой). Здесь есть много объяснений.

Код JavaScript для проверки записи пользователя по схеме может быть:

1
2
3
4
5
6
7
8
9
var Ajv = require(‘ajv’);
var ajv = Ajv({allErrors: true});
var valid = ajv.validate(userSchema, userData);
if (valid) {
  console.log(‘User data is valid’);
} else {
  console.log(‘User data is INVALID!’);
  console.log(ajv.errors);
}

или для лучшей производительности: javascript var validate = ajv.compile(userSchema); var valid = validate(userData); if (!valid) console.log(validate.errors); javascript var validate = ajv.compile(userSchema); var valid = validate(userData); if (!valid) console.log(validate.errors);

Все примеры кода доступны в GitHub repo tutsplus-json-schema . Вы также можете попробовать это в браузере .

Ajv , валидатор, используемый в примере, является самым быстрым валидатором JSON-Schema для JavaScript. Я создал его, поэтому собираюсь использовать его в этом уроке.

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

  • быстро провалиться
  • чтобы избежать повреждения данных
  • упростить обработку кода
  • использовать проверочный код в тестах
  • такое широкое распространение, как XML
  • проще в обработке и более кратким, чем XML
  • доминирует в веб-разработке благодаря JavaScript
  • декларативный
  • легче поддерживать
  • могут быть поняты некодерами
  • не нужно писать код, можно использовать сторонние библиотеки с открытым исходным кодом
  • самое широкое применение среди всех стандартов для проверки JSON
  • очень зрелый (текущая версия 4, есть предложения для версии 5)
  • охватывает большую часть сценариев проверки
  • использует простые для анализа документы JSON для схем
  • независимая платформа
  • легко растяжимый
  • 30+ валидаторов для разных языков, в том числе 10+ для JavaScript, поэтому не нужно кодировать его самостоятельно

Этот учебник включает в себя несколько относительно простых задач, которые помогут вам лучше понять схему JSON и то, как ее можно использовать. Есть простые сценарии JavaScript, чтобы проверить, что вы сделали их правильно. Для их запуска вам нужно установить node.js (вам не нужно с этим работать). Просто установите nvm (менеджер версий узлов) и последнюю версию node.js:

1
2
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.29.0/install.sh |
nvm install 4

Вам также необходимо клонировать репозиторий и запустить npm install (он установит валидатор Ajv).

JSON-схема всегда является объектом. Его свойства называются «ключевые слова». Некоторые из них описывают правила для данных (например, «тип» и «свойства»), а некоторые описывают саму схему («$ schema», «id», «title», «description») – мы перейдем к их позже.

Данные действительны в соответствии со схемой, если они действительны для всех ключевых слов в этой схеме – это действительно просто.

Поскольку большинство данных JSON состоит из объектов с несколькими свойствами, ключевое слово «свойства» , вероятно, является наиболее часто используемым ключевым словом. Это относится только к объектам (см. Следующий раздел о том, что означает «применять»).

В приведенном выше примере вы могли заметить, что каждое свойство внутри ключевого слова «properties» описывает соответствующее свойство в ваших данных.

Значение каждого свойства само по себе является JSON-схемой – JSON-схема является рекурсивным стандартом. Каждое свойство в данных должно быть действительным в соответствии с соответствующей схемой в ключевом слове «properties» .

Здесь важно то, что ключевое слово «properties» не требует никаких свойств; он определяет только схемы для свойств, присутствующих в данных.

Например, если наша схема:

1
2
3
4
5
{
  “properties”: {
    “foo”: { “type”: “string” }
  }
}

тогда объекты со свойством «foo» или без него могут быть действительными согласно этой схеме:

1
{foo: “bar”}, {foo: “bar”, baz: 1}, {baz: 1}, {} // all valid

и только объекты, которые имеют свойство foo, не являющееся строкой, недопустимы:

1
{ foo: 1 } // invalid

Попробуйте этот пример в браузере .

Вы уже выяснили, что делает ключевое слово «тип» . Это, наверное, самое важное ключевое слово. Его значение (строка или массив строк) определяет, какой тип (или типы) данных должен быть действительным.

Как видно из приведенного выше примера, пользовательские данные должны быть объектом.

Большинство ключевых слов применяются к определенным типам данных – например, ключевое слово «свойства» применяется только к объектам, а ключевое слово «шаблон» применяется только к строкам.

Что значит «подать заявку»? Допустим, у нас действительно простая схема:

1
{ “pattern”: “^[0-9]+$” }

Вы можете ожидать, что, чтобы быть действительным согласно такой схеме, данные должны быть строкой, соответствующей шаблону:

1
“12345”

Но стандарт JSON-схемы указывает, что если ключевое слово не относится к типу данных, то данные действительны в соответствии с этим ключевым словом. Это означает, что любые данные, не относящиеся к типу «строка», действительны в соответствии с приведенной выше схемой – числа, массивы, объекты, логические значения и даже ноль. Если вы хотите, чтобы действительными были только строки, соответствующие шаблону, ваша схема должна быть:

1
2
3
4
{
  “type”: “string”,
  “pattern”: “^[0-9]+$”
}

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

Посмотрите на свойство «id» в примере пользователя. Он должен быть действительным в соответствии с этой схемой:

1
2
3
4
5
{
  “type”: [“string”, “integer”],
  “pattern”: “^[1-9][0-9]*$”,
  “minimum”: 1
}

Эта схема требует, чтобы действительные данные были либо «строкой», либо «целым числом» . Существует также ключевое слово «pattern», которое применяется только к строкам; требуется, чтобы строка состояла только из цифр и не начиналась с 0. Существует ключевое слово «минимум», которое применяется только к числам; для этого необходимо, чтобы число было не менее 1.

Другой, более подробный способ выразить то же самое требование:

1
2
3
4
5
6
{
  “anyOf”: [
    { “type”: “string”, “pattern”: “^[1-9][0-9]*$” },
    { “type”: “integer”, “minimum”: 1 },
  ]
}

Но из-за способа определения JSON-схемы эта схема эквивалентна первой, которая короче и быстрее проверяется в большинстве валидаторов.

Типы данных, которые вы можете использовать в схемах: «объект» , «массив» , «число» , «целое число» , «строка» , «логическое значение» и «ноль» . Обратите внимание, что «число» включает в себя «целое число» – все целые числа тоже являются числами.

Есть несколько ключевых слов для проверки чисел. Все ключевые слова в этом разделе относятся только к числам (включая целые числа).

«Минимум» и «максимум» говорят сами за себя. В дополнение к ним есть ключевые слова «exclusiveMinimum» и «exclusiveMaximum» . В нашем примере пользователя возраст пользователя должен быть целым числом 13 или больше. Если схема для возраста пользователя была:

1
2
3
4
5
{
  “type”: “integer”,
  “minimum”: 13,
  “exclusiveMinimum”: true
}

тогда эта схема потребовала бы, чтобы возраст пользователя был строго больше 13, то есть минимально допустимый возраст был бы 14.

Еще одно ключевое слово для проверки чисел – «multipOf» . Его имя также объясняет, что он делает, и вы можете проверить ссылку на ключевые слова JSON-схемы, чтобы увидеть, как это работает.

Есть также несколько ключевых слов для проверки строк. Все ключевые слова в этом разделе относятся только к строкам.

«MaxLength» и «minLength» требуют, чтобы строка была не длиннее или не короче заданного числа. Стандарт JSON-схемы требует, чтобы пара юникода, например символ эмодзи, считалась одним символом. JavaScript учитывает его как два символа при доступе к свойству .length строк.

Некоторые валидаторы определяют длины строк в соответствии со стандартом, а некоторые делают это способом JavaScript, что быстрее. Ajv позволяет вам указать, как определять длину строки, и по умолчанию это соответствует стандарту.

1
2
3
4
5
6
7
8
9
var schema = { “maxLength”: 2 };
 
var ajv = Ajv();
ajv.validate(schema, “😀😀”);
ajv.validate(schema, “😀😀!”);
 
var ajv = Ajv({unicode: false});
ajv.validate(schema, “😀”);
ajv.validate(schema, “😀!”);

Вы уже видели ключевое слово «pattern» в действии – оно просто требует, чтобы строка данных соответствовала регулярному выражению, определенному в соответствии с тем же стандартом, который используется в JavaScript. Смотрите пример ниже для схем и соответствующих регулярных выражений:

1
2
3
{“pattern”: “^[1-9][0-9]*$” };
{“pattern”: “^[A-Za-z]+$” };
{“pattern”: “^[0-9()\\-\\.\\s]+$”};

Ключевое слово «format» определяет семантическую проверку строк, таких как «email» , «date» или «date-time» в примере пользователя. JSON-схема также определяет форматы «uri» , «hostname» , «ipv4» и «ipv6» . Валидаторы по-разному определяют форматы, оптимизируя скорость проверки или правильность. Айв дает вам выбор:

1
2
3
4
5
6
7
var ajv = Ajv();
ajv.validate({“format”: “date”}, “2015-12-24”);
ajv.validate({“format”: “date”}, “2015-14-33”);
 
var ajv = Ajv({format: ‘full’);
ajv.validate({“format”: “date”}, “2015-12-24”);
ajv.validate({“format”: “date”}, “2015-14-33”);

Большинство валидаторов позволяют вам определять пользовательские форматы как регулярные выражения или проверочные функции. Мы могли бы определить собственный формат «телефон» для нашей схемы, чтобы использовать его в нескольких местах:

1
ajv.addFormat(‘phone’, /^[0-9()\-\.\s]+$/);

и тогда схема для свойства phone будет такой:

1
{ “type”: “string”, “format”: “phone” }

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

Поместите свой ответ в файл part1/task1/date_schema.json и запустите node part1/task1/validate чтобы проверить его.

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

Ключевое слово «required» перечисляет свойства, которые должны присутствовать в объекте, чтобы он был действительным. Как вы помните, ключевое слово «properties» не требует свойств, оно только проверяет их, если они присутствуют. «Required» дополняет «properties» , позволяя вам определить, какие свойства являются обязательными, а какие – необязательными.

Если бы у нас была эта схема:

1
2
3
4
5
6
{
  “properties”: {
    “foo”: { “type”: “string” }
  },
  “required”: [“foo”]
}

тогда все объекты без свойства foo будут недействительными.

Обратите внимание, что эта схема все еще не требует, чтобы наши данные были объектом – все остальные типы данных действительны в соответствии с ней. Чтобы требовать, чтобы наши данные были объектом, мы должны добавить к нему ключевое слово «тип» .

Попробуйте приведенный выше пример в браузере .

Ключевое слово «patternProperties» позволяет вам определять схемы, в соответствии с которыми значение свойства данных должно быть допустимым, если имя свойства соответствует регулярному выражению. Его можно комбинировать с ключевым словом «properties» в той же схеме.

Свойство feeds в пользовательском примере должно быть допустимым в соответствии с этой схемой:

1
2
3
4
5
6
7
{
  “type”: “object”,
  “patternProperties”: {
     “^[A-Za-z]+$”: { “type”: “boolean” }
  },
  “additionalProperties”: false
}

Чтобы быть действительными, feeds должны быть объектом со свойствами, имена которых состоят только из латинских букв и значения которых являются логическими.

Ключевое слово «AdditionalProperties» позволяет вам либо определить схему, в соответствии с которой все остальные ключевые слова (не используемые в «свойствах» и не соответствующие «patternProperties» ) должны быть действительными, либо полностью запретить другие свойства, как мы это делали в свойстве feeds схема выше.

В следующем примере «AdditionalProperties» используется для создания простой схемы для хеша целых чисел в определенном диапазоне:

01
02
03
04
05
06
07
08
09
10
11
12
13
var schema = {
  “type”: “object”,
  “additionalProperties” {
    “type”: “integer”,
    “minimum”: 0,
    “maximum”: 65535
  }
};
var validate = ajv.compile(schema);
validate({a:1,b:10,c:100});
validate({d:1,e:10,f:100000});
validate({g:1,h:10,i:10.5});
validate({j:1,k:10,l:’abc’});

Ключевые слова «maxProperties» и «minProperties» позволяют ограничить количество свойств в объекте. В нашем пользовательском примере схема для свойства address выглядит следующим образом:

1
2
3
4
5
6
{
  “type”: “object”,
  “additionalProperties”: { “type”: “string” },
  “maxProperties”: 6,
  “required”: [“street”, “postcode”, “city”, “country”]
}

Эта схема требует, чтобы адрес являлся объектом с необходимыми свойствами street , postcode , city и country , допускает два дополнительных свойства («maxProperties» равно 6) и требует, чтобы все свойства были строками.

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

Существует два типа таких требований к объекту: иметь некоторые другие свойства (это называется «зависимость от свойства») или удовлетворить некоторую схему («зависимость от схемы»).

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

1
2
3
4
5
6
7
8
9
{
  “properties”: {
    “connType”: { “enum”: [“relative”] },
    “relation”: { “type”: “string” }
  },
  “dependencies”: {
    “relation”: [“close”]
  }
}

Для этого требуется, connType свойство connType равно «относительному» (см. Ключевое слово «enum» ниже), а если свойство relation присутствует, это строка.

Это не требует наличия relation , но ключевое слово «зависимости» требует, чтобы ЕСЛИ свойство relation присутствовало, ТОЛЬКО свойство close тоже должно присутствовать.

В нашей схеме не определены правила проверки для свойства close , хотя из примера видно, что оно, вероятно, должно быть логическим. Один из способов исправить это упущение – изменить ключевое слово «зависимости», чтобы использовать «зависимость от схемы»:

1
2
3
4
5
6
7
8
“dependencies”: {
  “relation”: {
    “properties”: {
      “close”: { “type”: “boolean” },
    },
    “required”: [“close”]
  }
}

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

Обратите внимание, что схема в свойстве «отношение» в ключевом слове «зависимости» используется для проверки родительского объекта (т. Е. Соединения), а не значения свойства relation в данных.

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

1
2
3
4
5
6
{
  “human”: true,
  “name”: “Jane”,
  “gender”: “female”,
  “DOB”: “1985-08-12”
}

Пример машинного объекта:

1
2
3
4
{
  “model”: “TX1000”,
  “made”: “2013-08-29”
}

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

Поместите свой ответ в файл part1/task2/human_machine_schema.json и запустите node part1/task2/validate чтобы проверить его.

Подсказки: используйте ключевое слово «зависимости» и посмотрите в файле part1/task2/invalid.json чтобы увидеть, какие объекты должны быть недействительными.

Какие объекты, которые, вероятно, также должны быть недействительными, отсутствуют в файле invalid.json ?

Основным выводом из этой задачи является тот факт, что целью валидации является не только валидация всех действительных объектов как допустимых. Я много раз слышал этот аргумент: «Эта схема проверяет мои действительные данные как действительные, поэтому они верны». Этот аргумент неверен, потому что вам не нужно много делать для его достижения – пустая схема сделает работу, потому что он проверяет достоверность любых данных.

Я думаю, что основная цель проверки состоит в том, чтобы проверить недействительные данные как недействительные, и в этом вся сложность.

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

«MaxItems» и «minItems» требуют, чтобы массив содержал не больше (или не меньше), чем определенное количество элементов. В примере пользователя схема требует, чтобы количество соединений не превышало 150.

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

Если значением ключевого слова «items» является массив, то этот массив содержит схемы, в соответствии с которыми соответствующие элементы должны быть действительными:

1
2
3
4
5
6
7
{
  “type”: “array”,
  “items”: [
    { “type”: “integer” },
    { “type”: “string” }
  ]
}

Схема в простом приведенном выше примере требует, чтобы данные представляли собой массив, причем первый элемент является целым числом, а второй – строкой.

Как насчет предметов после этих двух? Схема выше не определяет никаких требований для других предметов. Они могут быть определены с помощью ключевого слова « AdditionalItems».

Ключевое слово « AdditionalItems» применяется только к ситуации, в которой ключевое слово «items» является массивом, и в данных содержится больше элементов, чем в ключевом слове «items» . Во всех остальных случаях (без ключевого слова items , это объект или в данных больше нет элементов) ключевое слово AdditionalItems будет игнорироваться независимо от его значения.

Если ключевое слово « AdditionalItems» имеет значение true, оно просто игнорируется. Если оно ложно и в данных содержится больше элементов, чем в ключевом слове «items», проверка завершается неудачно:

01
02
03
04
05
06
07
08
09
10
11
var schema = {
  “type”: “array”,
  “items”: [
    { “type”: “integer” },
    { “type”: “string” }
  ],
  “additionalItems”: false
};
 
var validate = ajv.compile(schema);
console.log(validate([1, “foo”, 3]));

Если ключевое слово « AdditionalItems» является объектом, то этот объект является схемой, в соответствии с которой все дополнительные элементы должны быть действительными:

01
02
03
04
05
06
07
08
09
10
11
12
13
var schema = {
  “type”: “array”,
  “items”: [
    { “type”: “integer” },
    { “type”: “string” }
  ],
  “additionalItems”: { “type”: “integer” }
};
 
var validate = ajv.compile(schema);
console.log(validate([1, “foo”, 3]));
console.log(validate([1, “foo”, 3, 4]));
console.log(validate([1, “foo”, “bar”]));

Пожалуйста, поэкспериментируйте с этими примерами, чтобы увидеть, как работают «items» и «AdditionalItems» .

Последнее ключевое слово, которое применяется к массивам, это «uniqueItems» . Если его значение равно true , оно просто требует, чтобы все элементы в массиве были разными.

Проверка ключевого слова «uniqueItems» может быть вычислительно дорогой, поэтому некоторые валидаторы решили не реализовывать ее или делать это только частично.

Ajv имеет возможность игнорировать это ключевое слово:

01
02
03
04
05
06
07
08
09
10
11
12
var schema = {
  “type”: “array”,
  “uniqueItems”: true
};
 
var ajv = Ajv();
ajv.validate(schema, [1, 2, 3]);
ajv.validate(schema, [1, 2, 2]);
 
var ajv = Ajv({uniqueItems: false});
ajv.validate(schema, [1, 2, 3]);
ajv.validate(schema, [1, 2, 2]);

Одним из способов создания объекта даты в JavaScript является передача от 2 до 7 чисел в конструктор Date:

1
2
var date = new Date(2015, 2, 15);
var date2 = new Date(year, month0, day, hour, minute, seconds, ms);

У вас есть массив. Создайте схему, которая проверит, что это допустимый список аргументов для конструктора Date.

Поместите свой ответ в файл part1/task3/date_args_schema.json и запустите node part1/task3/validate чтобы проверить его.

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

В примере пользователя он используется для определения gender свойства внутри personal свойства как «мужской» или «женский». Он также используется для определения свойства connType в пользовательских подключениях.

Ключевое слово «enum» может использоваться с любыми типами значений, не только со строками и числами, хотя это не очень распространено.

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

1
2
3
4
“properties”: {
  “connType”: { “enum”: [“relative”] },
  …
}

Предложения для следующей версии (v5) стандарта JSON-Schema включают ключевое слово «константа», чтобы сделать то же самое.

Ajv позволяет использовать «константу» и некоторые другие ключевые слова из v5:

1
2
3
4
5
6
7
8
var schema = {
  “constant”: “relative”
};
 
var ajv = Ajv({v5: true});
var validate = ajv.compile(schema);
validate(“relative”);
validate(“other”);

Есть несколько ключевых слов, которые позволяют вам определить расширенную логику, включающую проверку по нескольким схемам. Все ключевые слова в этом разделе относятся ко всем типам данных.

В нашем примере пользователя используется ключевое слово «oneOf» для определения требований к пользовательскому соединению. Это ключевое слово допустимо, если данные успешно проверены по одной схеме внутри массива.

Если данные являются недействительными по всем схемам в ключевом слове «oneOf» или действительны по двум или более схемам, то данные являются недействительными.

Давайте посмотрим более внимательно на наш пример:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  …
  “oneOf”: [
    {
      “properties”: {
        “connType”: { “enum”: [“relative”] },
        “relation”: { “type”: “string” }
      },
      “dependencies”: {
        “relation”: [“close”]
      }
    },
    {
      “properties”: {
        “connType”: { “enum”: [“friend”, “colleague”, “other”] },
        “relation”: { “not”: {} },
        “close”: { “not”: {} }
      }
    }
  ],
  …
}

Приведенная выше схема требует, чтобы пользовательское соединение было либо «относительным» (свойство connType ), и в этом случае оно может иметь свойства relation (строка) и close (логическое), либо один из типов «друг», «коллега» или «другое» ( в этом случае он не должен иметь relation свойств и close ).

Эти схемы для подключения пользователя являются взаимоисключающими, потому что нет данных, которые могли бы удовлетворить их обоих. Поэтому, если соединение допустимо и имеет тип «относительный», нет смысла проверять его по второй схеме – оно всегда будет недействительным. Тем не менее, любой валидатор всегда будет проверять данные по обеим схемам, чтобы убедиться, что они действительны только по одной схеме.

Есть еще одно ключевое слово, которое позволяет вам избежать этого: «anyOf» . Это ключевое слово просто требует, чтобы данные были действительными согласно некоторой схеме в массиве (возможно, нескольким схемам).

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

Использование «oneOf» в тех случаях, когда «anyOf» выполняет одинаково хорошую работу, является очень распространенной ошибкой, которая отрицательно влияет на производительность проверки.

Наш пользовательский пример также выиграл бы от замены «oneOf» на «anyOf» .

Однако в некоторых случаях нам действительно необходимо ключевое слово «oneOf» :

1
2
3
4
5
6
7
{
  “type”: “string”,
  “oneOf”: [
    { “pattern”: “apple” }
    { “pattern”: “orange” }
  ]
}

Приведенная выше схема будет успешно проверять строки, которые упоминают апельсины или яблоки, но не оба (и существуют строки, которые могут упомянуть оба). Если это то, что вам нужно, тогда вам нужно использовать «oneOf» .

По сравнению с логическими операторами, anyOf – это логическое ИЛИ, а oneOf – XOR (исключающее ИЛИ). Тот факт, что JavaScript (и многие другие языки) не определяют операторы для исключающего ИЛИ, показывает, что это редко требуется.

Также есть ключевое слово «allOf» . Требуется, чтобы данные были действительны по всем схемам в массиве:

1
2
3
4
5
6
{
  “allOf”: [
    { “$ref”: “http://mynet.com/schemas/feed.json#” },
    { “maxProperties”: 5 }
  ]
}

Ключевое слово «$ ref» позволяет вам требовать, чтобы данные были действительными в соответствии со схемой в другом файле (или некоторой его части). Мы будем смотреть на это во второй части этого урока.

Другая ошибка заключается в том, что в схемах «oneOf» , «anyOf» и «allOf» содержится более чем абсолютно необходимое внутри схем. Например, в нашем пользовательском примере мы можем поместить в «anyOf» все требования, которым должно удовлетворять соединение.

Мы также могли бы излишне усложнить пример с яблоками и апельсинами:

1
2
3
4
5
6
{
  “oneOf”: [
    { “type”: “string”, “pattern”: “apple” }
    { “type”: “string”, “pattern”: “orange” }
  ]
}

Другое «логическое» ключевое слово – «нет» . Требуется, чтобы данные НЕ действовали в соответствии со схемой, которая является значением этого ключевого слова.

Например:

1
2
3
4
5
6
{
  “type”: “string”,
  “not”: {
    “pattern”: “apple”
  }
}

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

В примере пользователя ключевое слово «not» используется для предотвращения использования некоторых свойств в одном из случаев в «oneOf» , хотя они определены:

1
2
3
4
5
6
7
{
  “properties”: {
    “connType”: { “enum”: [“friend”, “colleague”, “other”] },
    “relation”: { “not”: {} },
    “close”: { “not”: {} }
  }
}

Значение ключевого слова «not» в приведенном выше примере является пустой схемой. Пустая схема подтвердит правильность любого значения, а ключевое слово «not» сделает его недействительным. Таким образом, проверка схемы не удастся, если объект имеет relation свойства или close . Вы можете достичь того же самого с помощью комбинации «не» и «обязательных» ключевых слов.

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

01
02
03
04
05
06
07
08
09
10
{
  “not”: {
    “items”: {
      “not: {
        “type”: “integer”,
        “minimum”: 5
      }
    }
  }
}

Приведенная выше схема требует, чтобы данные были массивом и содержали как минимум один целочисленный элемент, больший или равный 5.

Предложения V5 включают ключевое слово «содержит», чтобы удовлетворить это требование.

01
02
03
04
05
06
07
08
09
10
11
12
var schema = {
  “type”: “array”,
  “contains”: {
    “type”: “integer”,
    “minimum”: 5
  }
};
 
var ajv = Ajv({v5: true});
var validate = ajv.compile(schema);
validate([3, 4, 5]);
validate([1, 2, 3]);

У вас есть база данных пользователей, которые соответствуют схеме из примера пользователя. Создайте схему, в соответствии с которой будут действовать только те пользователи, которые удовлетворяют всем этим критериям:

  • незамужние мужчины моложе 21 года или старше 60 лет
  • иметь 5 или меньше соединений
  • подписаться на 3 или менее каналов

Поместите свой ответ в файл part1/task4/filter_schema.json и запустите node part1/task4/validate чтобы проверить его.

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

Некоторые ключевые слова, используемые в примере с пользователем, напрямую не влияют на проверку, но они описывают саму схему.

Ключевое слово «$ schema» определяет URI мета-схемы для схемы. Сама схема является документом JSON, и ее можно проверить с помощью JSON-схемы. JSON-схема, которая определяет любую JSON-схему, называется мета-схемой. URI для мета-схемы для черновика 4 стандарта JSON-схемы: http://json-schema.org/draft-04/schema .

Если вы расширяете стандарт, рекомендуется использовать другое значение свойства «$ schema» .

«Id» – это URI схемы. Его можно использовать для ссылки на схему (или некоторую ее часть) из другой схемы с помощью ключевого слова «$ ref» – см. Вторую часть руководства. Большинство валидаторов, включая Ajv, допускают любую строку как «id» . Согласно стандарту, идентификатор схемы должен быть допустимым URI, который можно использовать для загрузки схемы.

Вы также можете использовать «title» и «description» для описания схемы. Они не используются во время проверки. Оба эти ключевых слова могут использоваться на любом уровне внутри схемы для описания некоторых ее частей, как это делается в примере с пользователем.

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

Поместите свой ответ в файл part1/task5/invalid_user.json и запустите node part1/task5/validate чтобы проверить его.

Что все еще не так с нашей пользовательской схемой?

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

Мы также будем:

  • использовать схему для определения значений по умолчанию
  • отфильтровать дополнительные свойства из данных
  • использовать ключевые слова, включенные в предложения для версии 5 стандарта JSON-схемы
  • определить новые ключевые слова проверки
  • сравнить существующие валидаторы JSON-схемы

Спасибо за прочтение!