UNPKG

21 kBMarkdownView Raw
1# ajv-keywords
2
3Custom JSON-Schema keywords for [Ajv](https://github.com/epoberezkin/ajv) validator
4
5[![Build Status](https://travis-ci.org/epoberezkin/ajv-keywords.svg?branch=master)](https://travis-ci.org/epoberezkin/ajv-keywords)
6[![npm version](https://badge.fury.io/js/ajv-keywords.svg)](https://www.npmjs.com/package/ajv-keywords)
7[![npm downloads](https://img.shields.io/npm/dm/ajv-keywords.svg)](https://www.npmjs.com/package/ajv-keywords)
8[![Coverage Status](https://coveralls.io/repos/github/epoberezkin/ajv-keywords/badge.svg?branch=master)](https://coveralls.io/github/epoberezkin/ajv-keywords?branch=master)
9[![Greenkeeper badge](https://badges.greenkeeper.io/epoberezkin/ajv-keywords.svg)](https://greenkeeper.io/)
10[![Gitter](https://img.shields.io/gitter/room/ajv-validator/ajv.svg)](https://gitter.im/ajv-validator/ajv)
11
12
13## Contents
14
15- [Install](#install)
16- [Usage](#usage)
17- [Keywords](#keywords)
18 - [typeof](#typeof)
19 - [instanceof](#instanceof)
20 - [range and exclusiveRange](#range-and-exclusiverange)
21 - [if/then/else](#ifthenelse)
22 - [switch](#switch)
23 - [select/selectCases/selectDefault](#selectselectcasesselectdefault) (BETA)
24 - [patternRequired](#patternrequired)
25 - [prohibited](#prohibited)
26 - [deepProperties](#deepproperties)
27 - [deepRequired](#deeprequired)
28 - [uniqueItemProperties](#uniqueitemproperties)
29 - [regexp](#regexp)
30 - [formatMaximum / formatMinimum and formatExclusiveMaximum / formatExclusiveMinimum](#formatmaximum--formatminimum-and-formatexclusivemaximum--formatexclusiveminimum)
31 - [dynamicDefaults](#dynamicdefaults)
32- [License](#license)
33
34
35## Install
36
37```
38npm install ajv-keywords
39```
40
41
42## Usage
43
44To add all available keywords:
45
46```javascript
47var Ajv = require('ajv');
48var ajv = new Ajv;
49require('ajv-keywords')(ajv);
50
51ajv.validate({ instanceof: 'RegExp' }, /.*/); // true
52ajv.validate({ instanceof: 'RegExp' }, '.*'); // false
53```
54
55To add a single keyword:
56
57```javascript
58require('ajv-keywords')(ajv, 'instanceof');
59```
60
61To add multiple keywords:
62
63```javascript
64require('ajv-keywords')(ajv, ['typeof', 'instanceof']);
65```
66
67To add a single keyword in browser (to avoid adding unused code):
68
69```javascript
70require('ajv-keywords/keywords/instanceof')(ajv);
71```
72
73
74## Keywords
75
76### `typeof`
77
78Based on JavaScript `typeof` operation.
79
80The value of the keyword should be a string (`"undefined"`, `"string"`, `"number"`, `"object"`, `"function"`, `"boolean"` or `"symbol"`) or array of strings.
81
82To pass validation the result of `typeof` operation on the value should be equal to the string (or one of the strings in the array).
83
84```
85ajv.validate({ typeof: 'undefined' }, undefined); // true
86ajv.validate({ typeof: 'undefined' }, null); // false
87ajv.validate({ typeof: ['undefined', 'object'] }, null); // true
88```
89
90
91### `instanceof`
92
93Based on JavaScript `instanceof` operation.
94
95The value of the keyword should be a string (`"Object"`, `"Array"`, `"Function"`, `"Number"`, `"String"`, `"Date"`, `"RegExp"` or `"Buffer"`) or array of strings.
96
97To pass validation the result of `data instanceof ...` operation on the value should be true:
98
99```
100ajv.validate({ instanceof: 'Array' }, []); // true
101ajv.validate({ instanceof: 'Array' }, {}); // false
102ajv.validate({ instanceof: ['Array', 'Function'] }, function(){}); // true
103```
104
105You can add your own constructor function to be recognised by this keyword:
106
107```javascript
108function MyClass() {}
109var instanceofDefinition = require('ajv-keywords').get('instanceof').definition;
110// or require('ajv-keywords/keywords/instanceof').definition;
111instanceofDefinition.CONSTRUCTORS.MyClass = MyClass;
112
113ajv.validate({ instanceof: 'MyClass' }, new MyClass); // true
114```
115
116
117### `range` and `exclusiveRange`
118
119Syntax sugar for the combination of minimum and maximum keywords, also fails schema compilation if there are no numbers in the range.
120
121The value of this keyword must be the array consisting of two numbers, the second must be greater or equal than the first one.
122
123If the validated value is not a number the validation passes, otherwise to pass validation the value should be greater (or equal) than the first number and smaller (or equal) than the second number in the array. If `exclusiveRange` keyword is present in the same schema and its value is true, the validated value must not be equal to the range boundaries.
124
125```javascript
126var schema = { range: [1, 3] };
127ajv.validate(schema, 1); // true
128ajv.validate(schema, 2); // true
129ajv.validate(schema, 3); // true
130ajv.validate(schema, 0.99); // false
131ajv.validate(schema, 3.01); // false
132
133var schema = { range: [1, 3], exclusiveRange: true };
134ajv.validate(schema, 1.01); // true
135ajv.validate(schema, 2); // true
136ajv.validate(schema, 2.99); // true
137ajv.validate(schema, 1); // false
138ajv.validate(schema, 3); // false
139```
140
141
142### `if`/`then`/`else`
143
144These keywords allow to implement conditional validation. Their values should be valid JSON-schemas.
145
146If the data is valid according to the sub-schema in `if` keyword, then the result is equal to the result of data validation against the sub-schema in `then` keyword, otherwise - in `else` keyword (if `else` is absent, the validation succeeds).
147
148```javascript
149require('ajv-keywords')(ajv, 'if');
150
151var schema = {
152 type: 'array',
153 items: {
154 type: 'integer',
155 minimum: 1,
156 if: { maximum: 10 },
157 then: { multipleOf: 2 },
158 else: { multipleOf: 5 }
159 }
160};
161
162var validItems = [ 2, 4, 6, 8, 10, 15, 20, 25 ]; // etc.
163
164var invalidItems = [ 1, 3, 5, 11, 12 ]; // etc.
165
166ajv.validate(schema, validItems); // true
167ajv.validate(schema, invalidItems); // false
168```
169
170This keyword is [proposed](https://github.com/json-schema-org/json-schema-spec/issues/180) for the future version of JSON-Schema standard.
171
172
173### `switch`
174
175This keyword allows to perform advanced conditional validation.
176
177The value of the keyword is the array of if/then clauses. Each clause is the object with the following properties:
178
179- `if` (optional) - the value is JSON-schema
180- `then` (required) - the value is JSON-schema or boolean
181- `continue` (optional) - the value is boolean
182
183The validation process is dynamic; all clauses are executed sequentially in the following way:
184
1851. `if`:
186 1. `if` property is JSON-schema according to which the data is:
187 1. valid => go to step 2.
188 2. invalid => go to the NEXT clause, if this was the last clause the validation of `switch` SUCCEEDS.
189 2. `if` property is absent => go to step 2.
1902. `then`:
191 1. `then` property is `true` or it is JSON-schema according to which the data is valid => go to step 3.
192 2. `then` property is `false` or it is JSON-schema according to which the data is invalid => the validation of `switch` FAILS.
1933. `continue`:
194 1. `continue` property is `true` => go to the NEXT clause, if this was the last clause the validation of `switch` SUCCEEDS.
195 2. `continue` property is `false` or absent => validation of `switch` SUCCEEDS.
196
197```javascript
198require('ajv-keywords')(ajv, 'switch');
199
200var schema = {
201 type: 'array',
202 items: {
203 type: 'integer',
204 'switch': [
205 { if: { not: { minimum: 1 } }, then: false },
206 { if: { maximum: 10 }, then: true },
207 { if: { maximum: 100 }, then: { multipleOf: 10 } },
208 { if: { maximum: 1000 }, then: { multipleOf: 100 } },
209 { then: false }
210 ]
211 }
212};
213
214var validItems = [1, 5, 10, 20, 50, 100, 200, 500, 1000];
215
216var invalidItems = [1, 0, 2000, 11, 57, 123, 'foo'];
217```
218
219__Please note__: this keyword is moved here from Ajv, mainly to preserve backward compatibility. It is unlikely to become a standard. It's preferable to use `if`/`then`/`else` keywords if possible, as they are likely to be added to the standard. The above schema is equivalent to (for example):
220
221```javascript
222{
223 type: 'array',
224 items: {
225 type: 'integer',
226 if: { minimum: 1, maximum: 10 },
227 then: true,
228 else: {
229 if: { maximum: 100 },
230 then: { multipleOf: 10 },
231 else: {
232 if: { maximum: 1000 },
233 then: { multipleOf: 100 },
234 else: false
235 }
236 }
237 }
238}
239```
240
241
242### `select`/`selectCases`/`selectDefault`
243
244These keywords allow to choose the schema to validate the data based on the value of some property in the validated data.
245
246These keywords must be present in the same schema object (`selectDefault` is optional).
247
248The value of `select` keyword should be a [$data reference](https://github.com/epoberezkin/ajv/tree/5.0.2-beta.0#data-reference) that points to any primitive JSON type (string, number, boolean or null) in the data that is validated. You can also use a constant of primitive type as the value of this keyword (e.g., for debugging purposes).
249
250The value of `selectCases` keyword must be an object where each property name is a possible string representation of the value of `select` keyword and each property value is a corresponding schema (from draft-06 it can be boolean) that must be used to validate the data.
251
252The value of `selectDefault` keyword is a schema (from draft-06 it can be boolean) that must be used to validate the data in case `selectCases` has no key equal to the stringified value of `select` keyword.
253
254The validation succeeds in one of the following cases:
255- the validation of data using selected schema succeeds,
256- none of the schemas is selected for validation,
257- the value of select is undefined (no property in the data that the data reference points to).
258
259If `select` value (in data) is not a primitive type the validation fails.
260
261__Please note__: these keywords require Ajv `$data` option to support [$data reference](https://github.com/epoberezkin/ajv/tree/5.0.2-beta.0#data-reference).
262
263
264```javascript
265require('ajv-keywords')(ajv, 'select');
266
267var schema = {
268 type: object,
269 required: ['kind'],
270 properties: {
271 kind: { type: 'string' }
272 },
273 select: { $data: '0/kind' },
274 selectCases: {
275 foo: {
276 required: ['foo'],
277 properties: {
278 kind: {},
279 foo: { type: 'string' }
280 },
281 additionalProperties: false
282 },
283 bar: {
284 required: ['bar'],
285 properties: {
286 kind: {},
287 bar: { type: 'number' }
288 },
289 additionalProperties: false
290 }
291 },
292 selectDefault: {
293 propertyNames: {
294 not: { enum: ['foo', 'bar'] }
295 }
296 }
297};
298
299var validDataList = [
300 { kind: 'foo', foo: 'any' },
301 { kind: 'bar', bar: 1 },
302 { kind: 'anything_else', not_bar_or_foo: 'any value' }
303];
304
305var invalidDataList = [
306 { kind: 'foo' }, // no propery foo
307 { kind: 'bar' }, // no propery bar
308 { kind: 'foo', foo: 'any', another: 'any value' }, // additional property
309 { kind: 'bar', bar: 1, another: 'any value' }, // additional property
310 { kind: 'anything_else', foo: 'any' } // property foo not allowed
311 { kind: 'anything_else', bar: 1 } // property bar not allowed
312];
313```
314
315__Please note__: the current implementation is BETA. It does not allow using relative URIs in $ref keywords in schemas in `selectCases` and `selectDefault` that point outside of these schemas. The workaround is to use absolute URIs (that can point to any (sub-)schema added to Ajv, including those inside the current root schema where `select` is used). See [tests](https://github.com/epoberezkin/ajv-keywords/blob/v2.0.0/spec/tests/select.json#L314).
316
317
318### `patternRequired`
319
320This keyword allows to require the presence of properties that match some pattern(s).
321
322This keyword applies only to objects. If the data is not an object, the validation succeeds.
323
324The value of this keyword should be an array of strings, each string being a regular expression. For data object to be valid each regular expression in this array should match at least one property name in the data object.
325
326If the array contains multiple regular expressions, more than one expression can match the same property name.
327
328```javascript
329var schema = { patternRequired: [ 'f.*o', 'b.*r' ] };
330
331var validData = { foo: 1, bar: 2 };
332var alsoValidData = { foobar: 3 };
333
334var invalidDataList = [ {}, { foo: 1 }, { bar: 2 } ];
335```
336
337
338### `prohibited`
339
340This keyword allows to prohibit that any of the properties in the list is present in the object.
341
342This keyword applies only to objects. If the data is not an object, the validation succeeds.
343
344The value of this keyword should be an array of strings, each string being a property name. For data object to be valid none of the properties in this array should be present in the object.
345
346```
347var schema = { prohibited: ['foo', 'bar']};
348
349var validData = { baz: 1 };
350var alsoValidData = {};
351
352var invalidDataList = [
353 { foo: 1 },
354 { bar: 2 },
355 { foo: 1, bar: 2}
356];
357```
358
359
360### `deepProperties`
361
362This keyword allows to validate deep properties (identified by JSON pointers).
363
364This keyword applies only to objects. If the data is not an object, the validation succeeds.
365
366The value should be an object, where keys are JSON pointers to the data, starting from the current position in data, and the values are JSON schemas. For data object to be valid the value of each JSON pointer should be valid according to the corresponding schema.
367
368```javascript
369var schema = {
370 type: 'object',
371 deepProperties: {
372 "/users/1/role": { "enum": ["admin"] }
373 }
374};
375
376var validData = {
377 users: [
378 {},
379 {
380 id: 123,
381 role: 'admin'
382 }
383 ]
384};
385
386var alsoValidData = {
387 users: {
388 "1": {
389 id: 123,
390 role: 'admin'
391 }
392 }
393};
394
395var invalidData = {
396 users: [
397 {},
398 {
399 id: 123,
400 role: 'user'
401 }
402 ]
403};
404
405var alsoInvalidData = {
406 users: {
407 "1": {
408 id: 123,
409 role: 'user'
410 }
411 }
412};
413```
414
415
416### `deepRequired`
417
418This keyword allows to check that some deep properties (identified by JSON pointers) are available.
419
420This keyword applies only to objects. If the data is not an object, the validation succeeds.
421
422The value should be an array of JSON pointers to the data, starting from the current position in data. For data object to be valid each JSON pointer should be some existing part of the data.
423
424```javascript
425var schema = {
426 type: 'object',
427 deepRequired: ["/users/1/role"]
428};
429
430var validData = {
431 users: [
432 {},
433 {
434 id: 123,
435 role: 'admin'
436 }
437 ]
438};
439
440var invalidData = {
441 users: [
442 {},
443 {
444 id: 123
445 }
446 ]
447};
448```
449
450See [json-schema-org/json-schema-spec#203](https://github.com/json-schema-org/json-schema-spec/issues/203#issue-197211916) for an example of the equivalent schema without `deepRequired` keyword.
451
452
453### `uniqueItemProperties`
454
455The keyword allows to check that some properties in array items are unique.
456
457This keyword applies only to arrays. If the data is not an array, the validation succeeds.
458
459The value of this keyword must be an array of strings - property names that should have unique values across all items.
460
461```javascript
462var schema = { uniqueItemProperties: [ "id", "name" ] };
463
464var validData = [
465 { id: 1 },
466 { id: 2 },
467 { id: 3 }
468];
469
470var invalidData1 = [
471 { id: 1 },
472 { id: 1 },
473 { id: 3 }
474];
475
476var invalidData2 = [
477 { id: 1, name: "taco" },
478 { id: 2, name: "taco" }, // duplicate "name"
479 { id: 3, name: "salsa" }
480];
481```
482
483This keyword is contributed by [@blainesch](https://github.com/blainesch).
484
485
486### `regexp`
487
488This keyword allows to use regular expressions with flags in schemas (the standard `pattern` keyword does not support flags).
489
490This keyword applies only to strings. If the data is not a string, the validation succeeds.
491
492The value of this keyword can be either a string (the result of `regexp.toString()`) or an object with the properties `pattern` and `flags` (the same strings that should be passed to RegExp constructor).
493
494```javascript
495var schema = {
496 type: 'object',
497 properties: {
498 foo: { regexp: '/foo/i' },
499 bar: { regexp: { pattern: 'bar', flags: 'i' } }
500 }
501};
502
503var validData = {
504 foo: 'Food',
505 bar: 'Barmen'
506};
507
508var invalidData = {
509 foo: 'fog',
510 bar: 'bad'
511};
512```
513
514
515### `formatMaximum` / `formatMinimum` and `formatExclusiveMaximum` / `formatExclusiveMinimum`
516
517These keywords allow to define minimum/maximum constraints when the format keyword defines ordering.
518
519These keywords apply only to strings. If the data is not a string, the validation succeeds.
520
521The value of keyword `formatMaximum` (`formatMinimum`) should be a string. This value is the maximum (minimum) allowed value for the data to be valid as determined by `format` keyword.
522
523When this keyword is added, it defines comparison rules for formats `"date"`, `"time"` and `"date-time". Custom formats also can have comparison rules. See [addFormat](https://github.com/epoberezkin/ajv#api-addformat) method.
524
525The value of keyword `formatExclusiveMaximum` (`formatExclusiveMinimum`) should be a boolean value. These keyword cannot be used without `formatMaximum` (`formatMinimum`). If this keyword value is equal to `true`, the data to be valid should not be equal to the value in `formatMaximum` (`formatMinimum`) keyword.
526
527```javascript
528require('ajv-keywords')(ajv, ['formatMinimum', 'formatMaximum']);
529
530var schema = {
531 format: 'date',
532 formatMinimum: '2016-02-06',
533 formatMaximum: '2016-12-27',
534 formatExclusiveMaximum: true
535}
536
537var validDataList = ['2016-02-06', '2016-12-26', 1];
538
539var invalidDataList = ['2016-02-05', '2016-12-27', 'abc'];
540```
541
542
543### `dynamicDefaults`
544
545This keyword allows to assign dynamic defaults to properties, such as timestamps, unique IDs etc.
546
547This keyword only works if `useDefaults` options is used and not inside `anyOf` keywrods etc., in the same way as [default keyword treated by Ajv](https://github.com/epoberezkin/ajv#assigning-defaults).
548
549The keyword should be added on the object level. Its value should be an object with each property corresponding to a property name, in the same way as in standard `properties` keyword. The value of each property can be:
550
551- an identifier of default function (a string)
552- an object with properties `func` (an identifier) and `args` (an object with parameters that will be passed to this function during schema compilation - see examples).
553
554The properties used in `dynamicDefaults` should not be added to `required` keyword (or validation will fail), because unlike `default` this keyword is processed after validation.
555
556There are several predefined dynamic default functions:
557
558- `"timestamp"` - current timestamp in milliseconds
559- `"datetime"` - current date and time as string (ISO, valid according to `date-time` format)
560- `"date"` - current date as string (ISO, valid according to `date` format)
561- `"time"` - current time as string (ISO, valid according to `time` format)
562- `"random"` - pseudo-random number in [0, 1) interval
563- `"randomint"` - pseudo-random integer number. If string is used as a property value, the function will randomly return 0 or 1. If object `{func: 'randomint', max: N}` is used then the default will be an integer number in [0, N) interval.
564- `"seq"` - sequential integer number starting from 0. If string is used as a property value, the default sequence will be used. If object `{func: 'seq', name: 'foo'}` is used then the sequence with name `"foo"` will be used. Sequences are global, even if different ajv instances are used.
565
566```javascript
567var schema = {
568 type: 'object',
569 dynamicDefaults: {
570 ts: 'datetime',
571 r: { func: 'randomint', max: 100 },
572 id: { func: 'seq', name: 'id' }
573 },
574 properties: {
575 ts: {
576 type: 'string',
577 format: 'datetime'
578 },
579 r: {
580 type: 'integer',
581 minimum: 0,
582 maximum: 100,
583 exclusiveMaximum: true
584 },
585 id: {
586 type: 'integer',
587 minimum: 0
588 }
589 }
590};
591
592var data = {};
593ajv.validate(data); // true
594data; // { ts: '2016-12-01T22:07:28.829Z', r: 25, id: 0 }
595
596var data1 = {};
597ajv.validate(data1); // true
598data1; // { ts: '2016-12-01T22:07:29.832Z', r: 68, id: 1 }
599
600ajv.validate(data1); // true
601data1; // didn't change, as all properties were defined
602```
603
604You can add your own dynamic default function to be recognised by this keyword:
605
606```javascript
607var uuid = require('uuid');
608
609function uuidV4() { return uuid.v4(); }
610
611var definition = require('ajv-keywords').get('dynamicDefaults').definition;
612// or require('ajv-keywords/keywords/dynamicDefaults').definition;
613definition.DEFAULTS.uuid = uuidV4;
614
615var schema = {
616 dynamicDefaults: { id: 'uuid' },
617 properties: { id: { type: 'string', format: 'uuid' } }
618};
619
620var data = {};
621ajv.validate(schema, data); // true
622data; // { id: 'a1183fbe-697b-4030-9bcc-cfeb282a9150' };
623
624var data1 = {};
625ajv.validate(schema, data1); // true
626data1; // { id: '5b008de7-1669-467a-a5c6-70fa244d7209' }
627```
628
629You also can define dynamic default that accepts parameters, e.g. version of uuid:
630
631```javascript
632var uuid = require('uuid');
633
634function getUuid(args) {
635 var version = 'v' + (arvs && args.v || 4);
636 return function() {
637 return uuid[version]();
638 };
639}
640
641var definition = require('ajv-keywords').get('dynamicDefaults').definition;
642definition.DEFAULTS.uuid = getUuid;
643
644var schema = {
645 dynamicDefaults: {
646 id1: 'uuid', // v4
647 id2: { func: 'uuid', v: 4 }, // v4
648 id3: { func: 'uuid', v: 1 } // v1
649 }
650};
651```
652
653
654## License
655
656[MIT](https://github.com/epoberezkin/ajv-keywords/blob/master/LICENSE)