UNPKG

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