ajv Documentation

The fastest JSON schema Validator. Supports JSON Schema draft-04/06/07/2019-09/2020-12 and JSON Type Definition (RFC8927)

14,701 stars TypeScript TypeScript 1 file ยท ~17,935 tokens #ajv#json-schema#validator
RAW Doc

File: README.md

 

Ajv JSON schema validator

The fastest JSON validator for Node.js and browser.

Supports JSON Schema draft-04/06/07/2019-09/2020-12 (draft-04 support requires ajv-draft-04 package) and JSON Type Definition RFC8927.







Ajv sponsors

Contributing

More than 100 people contributed to Ajv, and we would love to have you join the development. We welcome implementing new features that will benefit many users and ideas to improve our documentation.

Please review Contributing guidelines and Code components.

Documentation

All documentation is available on the Ajv website.

Some useful site links:

Please [sponsor Ajv development](https://github.com/sponsors/epoberezkin)

Since I asked to support Ajv development 40 people and 6 organizations contributed via GitHub and OpenCollective - this support helped receiving the MOSS grant!

Your continuing support is very important - the funds will be used to develop and maintain Ajv once the next major version is released.

Please sponsor Ajv via:

Thank you.

Open Collective sponsors

























Performance

Ajv generates code to turn JSON Schemas into super-fast validation functions that are efficient for v8 optimization.

Currently Ajv is the fastest and the most standard compliant validator according to these benchmarks:

Performance of different validators by json-schema-benchmark:

Features

Install

To install version 8:

text
npm install ajv

Getting started

Try it in the Node.js REPL: https://runkit.com/npm/ajv

In JavaScript:

javascript
// or ESM/TypeScript import
import Ajv from "ajv"
// Node.js require:
const Ajv = require("ajv")

const ajv = new Ajv() // options can be passed, e.g. {allErrors: true}

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

const data = {
  foo: 1,
  bar: "abc",
}

const validate = ajv.compile(schema)
const valid = validate(data)
if (!valid) console.log(validate.errors)

Learn how to use Ajv and see more examples in the Guide: getting started

Changes history

See https://github.com/ajv-validator/ajv/releases

Please note: Changes in version 8.0.0

Version 7.0.0

Version 6.0.0.

Code of conduct

Please review and follow the Code of conduct.

Please report any unacceptable behaviour to [email protected] - it will be reviewed by the project team.

Security contact

To report a security vulnerability, please use the
Tidelift security contact.
Tidelift will coordinate the fix and disclosure. Please do NOT report security vulnerabilities via GitHub issues.

Open-source software support

Ajv is a part of Tidelift subscription - it provides a centralised support to open-source software users, in addition to the support provided by software maintainers.

License

MIT


File: docs/guide/async-validation.md

Asynchronous validation

You can define formats and keywords that perform validation asynchronously by accessing database or some other service. You should add async: true in the keyword or format definition (see addFormat, addKeyword and User-defined keywords).

If your schema uses asynchronous formats/keywords or refers to some schema that contains them it should have "$async": true keyword so that Ajv can compile it correctly. If asynchronous format/keyword or reference to asynchronous schema is used in the schema without $async keyword Ajv will throw an exception during schema compilation.

::: warning Use $async: true in referenced schemas
All asynchronous subschemas that are referenced from the current or other schemas should have "$async": true keyword as well, otherwise the schema compilation will fail.
:::

Validation function for an asynchronous format/keyword should return a promise that resolves with true or false (or rejects with new Ajv.ValidationError(errors) if you want to return errors from the keyword function).

Ajv compiles asynchronous schemas to async functions. Async functions are supported in Node.js 7+ and all modern browsers. You can supply a transpiler as a function via processCode option. See Options.

The compiled validation function has $async: true property (if the schema is asynchronous), so you can differentiate these functions if you are using both synchronous and asynchronous schemas.

Validation result will be a promise that resolves with validated data or rejects with an exception Ajv.ValidationError that contains the array of validation errors in errors property.

Example:

javascript
const ajv = new Ajv()

ajv.addKeyword({
  keyword: "idExists",
  async: true,
  type: "number",
  validate: checkIdExists,
})

async function checkIdExists(schema, data) {
  // this is just an example, you would want to avoid SQL injection in your code
  const rows = await sql(`SELECT id FROM ${schema.table} WHERE id = ${data}`)
  return !!rows.length // true if record is found
}

const schema = {
  $async: true,
  properties: {
    userId: {
      type: "integer",
      idExists: {table: "users"},
    },
    postId: {
      type: "integer",
      idExists: {table: "posts"},
    },
  },
}

const validate = ajv.compile(schema)

validate({userId: 1, postId: 19})
  .then(function (data) {
    console.log("Data is valid", data) // { userId: 1, postId: 19 }
  })
  .catch(function (err) {
    if (!(err instanceof Ajv.ValidationError)) throw err
    // data is invalid
    console.log("Validation errors:", err.errors)
  })

Using transpilers

javascript
const ajv = new Ajv({processCode: transpileFunc})
const validate = ajv.compile(schema) // transpiled es7 async function
validate(data).then(successFunc).catch(errorFunc)

See Options.


File: docs/guide/combining-schemas.md

Combining schemas

[[toc]]

Combining schemas with $ref

You can structure your validation logic across multiple schema files and have schemas reference each other using $ref keyword.

Example:

javascript
const schema = {
  $id: "http://example.com/schemas/schema.json",
  type: "object",
  properties: {
    foo: {$ref: "defs.json#/definitions/int"},
    bar: {$ref: "defs.json#/definitions/str"},
  },
}

const defsSchema = {
  $id: "http://example.com/schemas/defs.json",
  definitions: {
    int: {type: "integer"},
    str: {type: "string"},
  },
}

Now to compile your schema you can either pass all schemas to Ajv instance:

javascript
const ajv = new Ajv({schemas: [schema, defsSchema]})
const validate = ajv.getSchema("http://example.com/schemas/schema.json")

or use addSchema method:

javascript
const ajv = new Ajv()
const validate = ajv.addSchema(defsSchema).compile(schema)

See Options and addSchema method.

::: tip Reference resolution

  • $ref is resolved as the uri-reference using schema $id as the base URI (see the example).
  • References can be recursive (and mutually recursive) to implement the schemas for different data structures (such as linked lists, trees, graphs, etc.).
  • You don't have to host your schema files at the URIs that you use as schema $id. These URIs are only used to identify the schemas, and according to JSON Schema specification validators should not expect to be able to download the schemas from these URIs.
  • The actual location of the schema file in the file system is not used.
  • You can pass the identifier of the schema as the second parameter of addSchema method or as a property name in schemas option. This identifier can be used instead of (or in addition to) schema $id.
  • You cannot have the same $id (or the schema identifier) used for more than one schema - the exception will be thrown.
  • You can implement dynamic resolution of the referenced schemas using compileAsync method. In this way you can store schemas in any system (files, web, database, etc.) and reference them without explicitly adding to Ajv instance. See Asynchronous schema compilation.
    :::

Extending recursive schemas

While statically defined $ref keyword allows to split schemas to multiple files, it is difficult to extend recursive schemas - the recursive reference(s) in the original schema points to the original schema, and not to the extended one. So in JSON Schema draft-07 the only available solution to extend the recursive schema was to redefine all sections of the original schema that have recursion.

It was particularly repetitive when extending meta-schema, as it has many recursive references, but even in a schema with a single recursive reference extending it was very verbose.

JSON Schema draft-2019-09 and the upcoming draft defined the mechanism for dynamic recursion using keywords $recursiveRef/$recursiveAnchor (draft-2019-09) or $dynamicRef/$dynamicAnchor (the next JSON Schema draft) that is somewhat similar to "open recursion" in functional programming.

Consider this recursive schema with static recursion:

javascript
const treeSchema = {
  $id: "https://example.com/tree",
  type: "object",
  required: ["data"],
  properties: {
    data: true,
    children: {
      type: "array",
      items: {$ref: "#"},
    },
  },
}

The only way to extend this schema to prohibit additional properties is by adding additionalProperties keyword right in the schema - this approach can be impossible if you do not control the source of the original schema. Ajv also provided the additional keywords in ajv-merge-patch package to extend schemas by treating them as plain JSON data. While this approach may work for you, it is non-standard and therefore not portable.

The new keywords for dynamic recursive references allow extending this schema without modifying it:

javascript
const treeSchema = {
  $id: "https://example.com/tree",
  $recursiveAnchor: true,
  type: "object",
  required: ["data"],
  properties: {
    data: true,
    children: {
      type: "array",
      items: {$recursiveRef: "#"},
    },
  },
}

const strictTreeSchema = {
  $id: "https://example.com/strict-tree",
  $recursiveAnchor: true,
  $ref: "tree",
  unevaluatedProperties: false,
}

import Ajv2019 from "ajv/dist/2019"
// const Ajv2019 = require("ajv/dist/2019")
const ajv = new Ajv2019({
  schemas: [treeSchema, strictTreeSchema],
})
const validate = ajv.getSchema("https://example.com/strict-tree")

See dynamic-refs test for the example using $dynamicAnchor/$dynamicRef.

At the moment Ajv implements the spec for dynamic recursive references with these limitations:

  • $recursiveAnchor/$dynamicAnchor can only be used in the schema root.
  • $recursiveRef/$dynamicRef can only be hash fragments, without URI.

Ajv also does not support dynamic references in asynchronous schemas (Ajv extension) - it is assumed that the referenced schema is synchronous, and there is no validation-time check for it.

$data reference

With $data option you can use values from the validated data as the values for the schema keywords. See proposal for more information about how it works.

$data reference is supported in the keywords: const, enum, format, maximum/minimum, exclusiveMaximum / exclusiveMinimum, maxLength / minLength, maxItems / minItems, maxProperties / minProperties, formatMaximum / formatMinimum, formatExclusiveMaximum / formatExclusiveMinimum, multipleOf, pattern, required, uniqueItems.

The value of "$data" should be a JSON-pointer to the data (the root is always the top level data object, even if the $data reference is inside a referenced subschema) or a relative JSON-pointer (it is relative to the current point in data; if the $data reference is inside a referenced subschema it cannot point to the data outside of the root level for this subschema).

Examples.

This schema requires that the value in property smaller is less or equal than the value in the property larger:

javascript
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

This schema requires that the properties have the same format as their field names:

javascript
const schema = {
  additionalProperties: {
    type: "string",
    format: {$data: "0#"},
  },
}

const validData = {
  "date-time": "1963-06-19T08:30:06.283185Z",
  email: "[email protected]",
}

$data reference is resolved safely - it won't throw even if some property is undefined. If $data resolves to undefined the validation succeeds (with the exclusion of const keyword). If $data resolves to incorrect type (e.g. not "number" for maximum keyword) the validation fails.

$merge and $patch keywords

With the package ajv-merge-patch you can use the keywords $merge and $patch that allow extending JSON Schemas with patches using formats JSON Merge Patch (RFC 7396) and JSON Patch (RFC 6902).

To add keywords $merge and $patch to Ajv instance use this code:

javascript
require("ajv-merge-patch")(ajv)

Examples.

Using $merge:

javascript
{
  $merge: {
    source: {
      type: "object",
      properties: {p: {type: "string"}},
      additionalProperties: false
    },
    with: {
      properties: {q: {type: "number"}}
    }
  }
}

Using $patch:

javascript
{
  $patch: {
    source: {
      type: "object",
      properties: {p: {type: "string"}},
      additionalProperties: false
    },
    with: [{op: "add", path: "/properties/q", value: {type: "number"}}]
  }
}

The schemas above are equivalent to this schema:

javascript
{
  type: "object",
  properties: {
    p: {type: "string"},
    q: {type: "number"}
  },
  additionalProperties: false
}

The properties source and with in the keywords $merge and $patch can use absolute or relative $ref to point to other schemas previously added to the Ajv instance or to the fragments of the current schema.

See the package ajv-merge-patch for more information.


File: docs/guide/environments.md

Execution environments

[[toc]]

Server-side Node.js

The main consideration for using Ajv server-side is to manage compiled schemas correctly, ensuring that the same schema is not compiled more than once.

Short-lived environments

Depending on the life-time of the environments, the benefits from "compile once - validate many times" model can be limited - you can consider using standalone validation code.

If you have a pre-defined set of schemas, you can:

  1. compile all schemas in the build step - you can either write your own script or use ajv-cli.
  2. generate and beautify standalone validation code - you can have all your schemas exported from one file.
  3. additionally, you can inline all dependencies on Ajv or ajv-formats using any bundling tools.
  4. deploy compiled schemas as part of your application or library (with or without dependency on Ajv, depending on whether you did step 3 and which validation keywords are used in the schemas)

Please see gajus/table package that pre-compiles schemas in this way.

Even if your schemas need to be stored in the database, you can still compile schemas once and store your validation functions alongside schemas in the database as well, loading them on demand.

Browsers

See Content Security Policy to decide how best to use Ajv in the browser for your use case.

Whether you compile schemas in the browser or use standalone validation code, it is recommended that you bundle them together with your application code.

If you need to use Ajv in several application bundles you can create a separate UMD bundles of Ajv using npm run bundle script.

In this case you need to load Ajv using the correct bundle, depending on which schema language and which version you need to use:

```html ``` ```html ``` ```html ```

This bundle can be used with different module systems; it creates global ajv/ajv2019/ajvJTD if no module system is found.

The browser bundles are available on cdnjs.

::: warning Some frameworks re-define require
Some frameworks, e.g. Dojo, may redefine global require in a way that is not compatible with CommonJS module format. In this case Ajv bundle has to be loaded before the framework and then you can use global ajv (see issue #234).
:::

::: warning Internet Explorer 11
Ajv v8 in IE 11 will not work straight out of the box. To use it either recompile it, or set the options unicodeRegExp to false and code: { es5: true }, and transpile the Ajv node module (see issue #1585).
:::

ES5 environments

You need to:

  • recompile Typescript to ES5 target - it is set to 2018 in the bundled compiled code.
  • generate ES5 validation code:
javascript
const ajv = new Ajv({code: {es5: true}})

See Advanced options.

CJS vs ESM exports

The default configuration of AJV is to generate code in ES6 with Common JS (CJS) exports. This can be changed by setting
the ES Modules(ESM) flag.

javascript
const ajv = new Ajv({code: {esm: true}})

Other JavaScript environments

Ajv is used in other JavaScript environments, including Electron apps, WeChat mini-apps and many others, where the same considerations apply as above:

  • compilation performance
  • restrictive content security policy
  • bundle size

If any of this is important, you may have better results with pre-compiled standalone validation code.

Command line interface

Ajv can be used from the terminal in any operating system supported by Node.js

CLI is available as a separate npm package ajv-cli.

It supports:

  • compiling JSON Schemas to test their validity
  • generating standalone validation code that exports validation function(s)
  • migrating schemas to draft-07 and draft-2019-09 (using json-schema-migrate)
  • validating data file(s) against JSON Schema
  • testing expected validity of data against JSON Schema
  • referenced schemas
  • user-defined meta-schemas, validation keywords and formats
  • files in JSON, JSON5, YAML, and JavaScript format
  • all Ajv options
  • reporting changes in data after validation in JSON-patch format

File: docs/guide/formats.md

Format validation

String formats

From version 7 Ajv does not include formats defined by JSON Schema specification - these and several other formats are provided by ajv-formats plugin.

To add all formats from this plugin:

```javascript const Ajv = require("ajv") const addFormats = require("ajv-formats")

const ajv = new Ajv()
addFormats(ajv)

text
</code-block>

<code-block title="TypeScript">
```typescript
import Ajv from "ajv"
import addFormats from "ajv-formats"

const ajv = new Ajv()
addFormats(ajv)

See ajv-formats documentation for further details.

It is recommended NOT to use "format" keyword implementations with untrusted data, as they may use potentially unsafe regular expressions (even though known issues are fixed) - see ReDoS attack.

::: danger Format validation of untrusted data
If you need to use "format" keyword to validate untrusted data, you MUST assess their suitability and safety for your validation scenarios.
:::

The following formats are defined in ajv-formats for string validation with "format" keyword:

  • date: full-date according to RFC3339.
  • time: time with optional time-zone.
  • date-time: date-time from the same source (time-zone is mandatory).
  • duration: duration from RFC3339
  • uri: full URI.
  • uri-reference: URI reference, including full and relative URIs.
  • uri-template: URI template according to RFC6570
  • url (deprecated): URL record.
  • email: email address.
  • hostname: host name according to RFC1034.
  • ipv4: IP address v4.
  • ipv6: IP address v6.
  • regex: tests whether a string is a valid regular expression by passing it to RegExp constructor.
  • uuid: Universally Unique Identifier according to RFC4122.
  • json-pointer: JSON-pointer according to RFC6901.
  • relative-json-pointer: relative JSON-pointer according to this draft.

::: warning Additional formats in ajv-formats-draft2019
JSON Schema draft-07 also defines formats iri, iri-reference, idn-hostname and idn-email for URLs, hostnames and emails with international characters. These formats are available in ajv-formats-draft2019 plugin.
:::

User-defined formats

You can add and replace any formats using addFormat method:

javascript
ajv.addFormat("identifier", /^a-z\$_[a-zA-Z$_0-9]*$/)

Ajv also allows defining the formats that would be applied to numbers only:

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

Formats and standalone validation code

If you use formats from ajv-formats package, standalone validation code will be supported out of the box.

::: warning Standalone code and Ajv versions
You need to make sure that ajv-formats imports the same version and the same code of ajv as the one you use in your application for standalone validation code to work (because of instanceof check that is currently used).

npm and other package managers may not update the version of ajv dependency of ajv-formats when you update version of ajv in your application - the workaround is to use clean npm installation.
:::

If you define your own formats, for standalone code generation to work you need to pass the code snippet that evaluates to an object with all defined formats to the option code.formats:

```javascript const {default: Ajv, _} = require("ajv") const ajv = new Ajv({code: {formats: _`require("./my_formats")`}}) ``` ```typescript import Ajv, {_} from "ajv" const ajv = new Ajv({code: {formats: _`require("./my_formats")`}}) ```

File: docs/guide/getting-started.md

Getting started

[[toc]]

Install

::: tip Node REPL
You can try Ajv without installing it in the Node.js REPL: https://runkit.com/npm/ajv
:::

To install Ajv version 8:

bash
npm install ajv

If you need to use Ajv with JSON Schema draft-04, you need to install Ajv version 6:

bash
npm install ajv@6

See Contributing on how to run the tests locally

Basic data validation

Ajv takes a schema for your JSON data and converts it into a very efficient JavaScript code
that validates your data according to the schema. To create a schema you can use either
JSON Schema or JSON Type Definition - check out Choosing schema language, they have
different advantages and disadvantages.

For example, to validate an object that has a required property "foo" (an integer number), an optional property "bar" (a string) and no other properties:

```javascript const Ajv = require("ajv") const ajv = new Ajv() // options can be passed, e.g. {allErrors: true}

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

const validate = ajv.compile(schema)

const data = {
foo: 1,
bar: "abc"
}

const valid = validate(data)
if (!valid) console.log(validate.errors)

text
</code-block>

<code-block title="JSON Type Definition">
```javascript
const Ajv = require("ajv/dist/jtd")
const ajv = new Ajv() // options can be passed, e.g. {allErrors: true}

const schema = {
  properties: {
    foo: {type: "int32"}
  },
  optionalProperties: {
    bar: {type: "string"}
  }
}


const validate = ajv.compile(schema)

const data = {
  foo: 1,
  bar: "abc"
}

const valid = validate(data)
if (!valid) console.log(validate.errors)

Ajv compiles schemas to functions and caches them in all cases (using the schema itself as a key in a Map), so that the next time the same schema object is used it won't be compiled again.

::: tip Best performance: compile and getSchema methods
The best performance is achieved when using compiled functions returned by compile or getSchema methods.

While execution of the compiled validation function is very fast, its compilation is
relatively slow, so you need to make sure that you compile schemas only once and
re-use compiled validation functions. See Managing multiple schemas.
:::

::: warning Save errors property
Every time a validation function (or ajv.validate) is called the errors property is overwritten. You need to copy the errors array reference to another variable if you want to use it later (e.g. in the callback). See Validation errors.
:::

Parsing and serializing JSON

Ajv can compile efficient parsers and serializers from JSON Type Definition schemas.

Serializing the data with a function specialized to your data shape can be more than 10x compared with JSON.stringify.

Parsing the data replaces the need for separate validation after generic parsing with JSON.parse (although validation itself is usually much faster than parsing). In case your JSON string is valid, the specialized parsing is approximately as fast as JSON.parse, but in case your JSON is invalid, the specialized parsing would fail much faster - so it can be very efficient in some scenarios.

For the same data structure, you can compile parser and serializer in this way:

```javascript const Ajv = require("ajv/dist/jtd") const ajv = new Ajv() // options can be passed, e.g. {allErrors: true}

const schema = {
properties: {
foo: {type: "int32"}
},
optionalProperties: {
bar: {type: "string"}
}
}

const serialize = ajv.compileSerializer(schema)

const data = {
foo: 1,
bar: "abc"
}

console.log(serialize(data))

const parse = ajv.compileParser(schema)

const json = '{"foo": 1, "bar": "abc"}'
const invalidJson = '{"unknown": "abc"}'

parseAndLog(json) // logs {foo: 1, bar: "abc"}
parseAndLog(invalidJson) // logs error and position

function parseAndLog(json) {
const data = parse(json)
if (data === undefined) {
console.log(parse.message) // error message from the last parse call
console.log(parse.position) // error position in string
} else {
console.log(data)
}
}

text
</code-block>
</code-group>

::: tip Lower parsing performance of empty schemas
You would have smaller performance benefits in case your schema contains some properties or other parts that are empty schemas (`{}`) - parser would call `JSON.parse` in this case.
:::

::: warning JTD discriminator schema
The performance of parsing discriminator schemas depends on the position of discriminator tag in the schema - the best parsing performance will be achieved if the tag is the first property - this is how compiled JTD serializers generate JSON in case of discriminator schemas.

Also, if discriminator tag were to be repeated in JSON, the second value would be ignored and the object still validated according to the first tag.
:::

::: warning Compiled parsers do NOT throw exceptions
Compiled parsers, unlike JSON.parse, do not throw the exception in case JSON string is not a valid JSON or in case data is invalid according to the schema. As soon as the parser determines that either JSON or data is invalid, it returns `undefined` and reports error and position via parsers properties `message` and `position`.
:::

---

## File: docs/guide/managing-schemas.md

# Managing schemas

[[toc]]

## Re-using validation functions

Ajv validation model is optimized for server side execution, when schema compilation happens only once and validation happens multiple times - this has a substantial performance benefit comparing with validators that interpret the schema in the process of validation.

Transition from template-based code generation in Ajv v6 to the tree-based in v7 brought:

- type-level safety against code injection via untrusted schemas
- more efficient validation code (via [tree optimizations](../codegen.md#code-optimization))
- smaller memory footprint of compiled functions (schemas are no longer serialized)
- smaller bundle size
- more maintainable code

These improvements cost slower schema compilation, and increased chance of re-compilation in case you pass a different schema object (see [#1413](https://github.com/ajv-validator/ajv/issues/1413)), so it is very important to manage schemas correctly, so they are only compiled once.

There are several approaches to manage compiled schemas.

## Standalone validation code

The motivation to pre-compile schemas:

- faster startup times
- lower memory footprint/bundle size
- compatible with strict content security policies
- almost no risk to compile schema more than once
- better for short-lived environments

See [Standalone validation code](../standalone) for the details.

There are scenarios when it can be not possible or difficult:

- dynamic or user-provided schemas - while you can do caching, it can be either difficult to implement or inefficient.
- user-defined keywords that use closures that are difficult to serialize as code.

## Compiling during initialization

The simplest approach is to compile all your schemas when the application starts, outside of the code that handles requests. It can be done simply in the module scope:

<code-group>
<code-block title="JavaScript">
```javascript
const Ajv = require("ajv").default
const schema_user = require("./schema_user.json")
const ajv = new Ajv()
const validate_user = ajv.compile(schema_user)





// this is just some abstract API framework
app.post("/user", async (cxt) => {
  if (validate_user(cxt.body)) {
    // create user
  } else {
    // report error
    cxt.status(400)
  }
})
```javascript import Ajv from "ajv" import * as schema_user from "./schema_user.json" const ajv = new Ajv() const validate_user = ajv.compile(schema_user)

interface User {
username: string
}

// this is just some abstract API framework
app.post("/user", async (cxt) => {
if (validate_user(cxt.body)) {
// create user
} else {
// report error
cxt.status(400)
}
})

text
</code-block>
</code-group>

::: warning Use single Ajv instance
It is recommended to use a single Ajv instance for the whole application, so if you use validation in more than one module, you should:

- require Ajv in a separate module responsible for validation
- compile all validators there
- export them to be used in multiple modules of your application
:::

## Using Ajv instance cache

Another, more effective approach, is to use Ajv instance cache to have all compiled validators available anywhere in your application from a single import.

In this case you would have a separate module where you instantiate Ajv and use this instance in your application.

You can load all schemas and add them to Ajv instance in a single `validation` module:

<code-group>
<code-block title="validation.js">
```javascript
const Ajv = require("ajv")
const schema_user = require("./schema_user.json")
const schema_document = require("./schema_document.json")
const ajv = exports.ajv = new Ajv()
ajv.addSchema(schema_user, "user")
ajv.addSchema(schema_document, "document")
```typescript import Ajv from "ajv" import * as schema_user from "./schema_user.json" import * as schema_document from "./schema_document.json" export const ajv = new Ajv() ajv.addSchema(schema_user, "user") ajv.addSchema(schema_document, "document") ```

And then you can import Ajv instance and access any schema in any application module, for example user module:

```javascript const {ajv} = require("./validation")

// this is just some abstract API framework
app.post("/user", async (cxt) => {
const validate = ajv.getSchema("user")
if (validate(cxt.body)) {
// create user
} else {
// report error
cxt.status(400)
}
})

text
</code-block>

<code-block title="users.ts">
```javascript
import ajv from "./validation"

interface User {
  username: string
}

// this is just some abstract API framework
app.post("/user", async (cxt) => {
  const validate = ajv.getSchema("user")
  if (validate(cxt.body)) {
    // create user
  } else {
    // report error
    cxt.status(400)
  }
})

::: tip On-demand vs preliminary compilation
In the example above, schema compilation happens only once, on the first API call, not at the application start-up time. It means that the application would start a bit faster, but the first API call would be a bit slower. If this is undesirable, you could, for example, call getSchema for all added schemas after they are added, then when getSchema is called inside route handler it would simply get compiled validation function from the instance cache.
:::

Cache key: schema vs key vs $id

In the example above, the key passed to the addSchema method was used to retrieve schemas from the cache. Other options are:

  • use schema root $id attribute. While it usually looks like URI, it does not mean Ajv downloads it from this URI - this is simply $id used to identify and access the schema. You can though configure Ajv to download schemas on demand - see Asynchronous schema loading
  • use schema object itself as a key to the cache (it is possible, because Ajv uses Map). This approach is not recommended, because it would only work if you pass the same instance of the schema object that was passed to addSchema method - it is easy to make a mistake that would result in schema being compiled every time it is used.

Pre-adding all schemas vs adding on demand

In the example above all schemas were added in advance. It is also possible, to add schemas as they are used - it can be helpful if there are many schemas. In this case, you need to check first whether the schema is already added by calling getSchema method - it would return undefined if not:

javascript
const schema_user = require("./schema_user.json")
let validate = ajv.getSchema("user")
if (!validate) {
  ajv.addSchema(schema_user, "user")
  validate = ajv.getSchema("user")
}

If your schema has $id attribute, for example:

```json { "$id": "https://example.com/user.json", "type": "object", "properties": { "username": {"type": "string"} }, required: ["username"] } ```

then the above logic can be simpler:

javascript
const schema_user = require("./schema_user.json")
const validate = ajv.getSchema("https://example.com/user.json")
              || ajv.compile(schema_user)

The above is possible because when the schema has $id attribute compile method both compiles the schema (returning the validation function) and adds it to the Ajv instance cache at the same time.

Asynchronous schema loading

There are cases when you need to have a large collection of schemas stored in some database or on the remote server. In this case you are likely to use schema $id as some resource identifier to retrieve it - either network URI or database ID.

You can use compileAsync method to asynchronously load the schemas as they are compiled, loading the schemas that are referenced from compiled schemas on demand. Ajv itself does not do any IO operations, it uses the function you supply via loadSchema option to load schema from the passed ID. This function should return Promise that resolves to the schema (you can use async function, as in the example).

Example:

javascript
const ajv = new Ajv({loadSchema: loadSchema})

ajv.compileAsync(schema).then(function (validate) {
  const valid = validate(data)
  // ...
})

async function loadSchema(uri) {
  const res = await request.json(uri)
  if (res.statusCode >= 400) throw new Error("Loading error: " + res.statusCode)
  return res.body
}

Caching schemas in your code

You can maintain cache of compiled schemas in your application independently from Ajv. It can be helpful in cases when you have multiple Ajv instances because, for example:

  • you need to compile different schemas with different options
  • you use both JSON Schema and JSON Type Definition schemas in one application
  • you have $id conflicts between different third party schemas you do not control

Whatever approach you use, you need to ensure that each schema is compiled only once.


File: docs/guide/modifying-data.md

Modifying data during validation

[[toc]]

General considerations

Ajv has several options that allow to modify data during validation:

  • removeAdditional - to remove properties not defined in the schema object.
  • useDefaults - to assign defaults from the schema to the validated data properties.
  • coerceTypes - to change data type, when possible, to match the type(s) in the schema.

You can also define keywords that modify data.

::: tip NOT possible to modify root data
It is not possible to modify the root data instance passed to the validation function, only data properties can be modified. This is related to how JavaScript passes parameters, and not a limitation of Ajv.
:::

::: warning Non-portable functionality
This functionality is non-standard - this is likely to be unsupported in other JSON Schema validator implementations.
:::

::: danger Unexpected results when modifying data
While pure schema validation produces the results independent of the keywords and subschema order, enabling any feature that may modify the data makes validation impure and its results are likely to depend on the order of evaluation of keywords and subschemas.

The order of evaluation of subschemas in keywords like allOf is always the same as the order of subschemas in the array.

On another hand, the order of evaluation of keywords, while consistent between validations and not dependent on how schema object is created, is neither documented nor guaranteed, so it can change in the future major versions (and, in rare cases, it can change in minor version - e.g. when there is bug that needs to be fixed).

It is strongly recommended to always put user-defined keywords that can mutate data in separate subschemas inside allOf keyword to make the order of evaluation unambiguous. The exceptions to this recommendation are pre-defined default and type keywords - they must remain in the same schema as other keywords.
:::

Removing additional properties

With option removeAdditional (added by andyscott) you can filter data during the validation.

This option modifies original data.

Example:

javascript
const ajv = new Ajv({removeAdditional: true})
const schema = {
  additionalProperties: false,
  properties: {
    foo: {type: "number"},
    bar: {
      additionalProperties: {type: "number"},
      properties: {
        baz: {type: "string"},
      },
    },
  },
}

const data = {
  foo: 0,
  additional1: 1, // will be removed; `additionalProperties` == false
  bar: {
    baz: "abc",
    additional2: 2, // will NOT be removed; `additionalProperties` != false
  },
}

const validate = ajv.compile(schema)

console.log(validate(data)) // true
console.log(data) // { "foo": 0, "bar": { "baz": "abc", "additional2": 2 }

If removeAdditional option in the example above were "all" then both additional1 and additional2 properties would have been removed.

If the option were "failing" then property additional1 would have been removed regardless of its value and property additional2 would have been removed only if its value were failing the schema in the inner additionalProperties (so in the example above it would have stayed because it passes the schema, but any non-number would have been removed).

::: warning Unexpected results when using removeAdditional with anyOf/oneOf
If you use removeAdditional option with additionalProperties keyword inside anyOf/oneOf keywords your validation can fail with this schema. To make it work as you expect, you have to use discriminated union with discriminator keyword (requires discriminator option).
:::

For example, with this non-discriminated union you will have unexpected results:

javascript
{
  type: "object",
  oneOf: [
    {
      properties: {
        foo: {type: "string"}
      },
      required: ["foo"],
      additionalProperties: false
    },
    {
      properties: {
        bar: {type: "integer"}
      },
      required: ["bar"],
      additionalProperties: false
    }
  ]
}

The intention of the schema above is to allow objects with either the string property "foo" or the integer property "bar", but not with both and not with any other properties.

With the option removeAdditional: true the validation will pass for the object { "foo": "abc"} but will fail for the object {"bar": 1}. It happens because while the first subschema in oneOf is validated, the property bar is removed because it is an additional property according to the standard (because it is not included in properties keyword in the same schema).

While this behaviour is unexpected (issues #129, #134), it is correct. To have the expected behaviour (both objects are allowed and additional properties are removed) the schema has to be refactored in this way:

javascript
{
  type: "object",
  properties: {
    foo: {type: "string"},
    bar: {type: "integer"}
  },
  additionalProperties: false,
  oneOf: [{required: ["foo"]}, {required: ["bar"]}]
}

The schema above is also more efficient - it will compile into a faster function.

For discriminated unions you could schemas with discriminator keyword (it requires discriminator: true option):

javascript
{
  type: "object",
  discriminator: {propertyName: "tag"},
  required: ["tag"],
  oneOf: [
    {
      properties: {
        tag: {const: "foo"},
        foo: {type: "string"}
      },
      required: ["foo"],
      additionalProperties: false
    },
    {
      properties: {
        tag: {const: "bar"},
        bar: {type: "integer"}
      },
      required: ["bar"],
      additionalProperties: false
    }
  ]
}

With this schema, only one subschema in oneOf will be evaluated, so removeAdditional option will work as expected.

See discriminator keyword.

Assigning defaults

With option useDefaults Ajv will assign values from default keyword in the schemas of properties and items (when it is the array of schemas) to the missing properties and items.

With the option value "empty" properties and items equal to null or "" (empty string) will be considered missing and assigned defaults.

This option modifies original data.

::: warning Defaults are deep-cloned
The default value is inserted in the generated validation code as a literal, so the value inserted in the data will be the deep clone of the default in the schema.
:::

Example 1 (default in properties):

javascript
const ajv = new Ajv({useDefaults: true})
const schema = {
  type: "object",
  properties: {
    foo: {type: "number"},
    bar: {type: "string", default: "baz"},
  },
  required: ["foo", "bar"],
}

const data = {foo: 1}

const validate = ajv.compile(schema)

console.log(validate(data)) // true
console.log(data) // { "foo": 1, "bar": "baz" }

Example 2 (default in items):

javascript
const schema = {
  type: "array",
  items: [{type: "number"}, {type: "string", default: "foo"}],
}

const data = [1]

const validate = ajv.compile(schema)

console.log(validate(data)) // true
console.log(data) // [ 1, "foo" ]

With useDefaults option default keywords throws exception during schema compilation when used in:

  • not in properties or items subschemas
  • in schemas inside anyOf, oneOf and not (see #42)
  • in if schema
  • in schemas generated by user-defined macro keywords

The strict mode option can change the behaviour for these unsupported defaults (strict: false to ignore them, "log" to log a warning).

See Strict mode.

::: tip Default with discriminator keyword
Defaults will be assigned in schemas inside oneOf in case discriminator keyword is used.
:::

Coercing data types

When you are validating user inputs all your data properties are usually strings. The option coerceTypes allows you to have your data types coerced to the types specified in your schema type keywords, both to pass the validation and to use the correctly typed data afterwards.

This option modifies original data.

::: warning Type coercion with scalar values
If you pass a scalar value to the validating function its type will be coerced and it will pass the validation, but the value of the variable you pass won't be updated because scalars are passed by value.
:::

Example 1:

javascript
const ajv = new Ajv({coerceTypes: true})
const schema = {
  type: "object",
  properties: {
    foo: {type: "number"},
    bar: {type: "boolean"},
  },
  required: ["foo", "bar"],
}

const data = {foo: "1", bar: "false"}

const validate = ajv.compile(schema)

console.log(validate(data)) // true
console.log(data) // { "foo": 1, "bar": false }

Example 2 (array coercions):

javascript
const ajv = new Ajv({coerceTypes: "array"})
const schema = {
  properties: {
    foo: {type: "array", items: {type: "number"}},
    bar: {type: "boolean"},
  },
}

const data = {foo: "1", bar: ["false"]}

const validate = ajv.compile(schema)

console.log(validate(data)) // true
console.log(data) // { "foo": [1], "bar": false }

The coercion rules, as you can see from the example, are different from JavaScript both to validate user input as expected and to have the coercion reversible (to correctly validate cases where different types are defined in subschemas of "anyOf" and other compound keywords).

See Type coercion rules for details.


File: docs/guide/schema-language.md


tags:

  • JTD

Choosing schema language

[[toc]]

JSON Type Definition

Ajv supports the new specification focussed on defining cross-platform types of JSON messages/payloads - JSON Type Definition (JTD). See the informal reference of JTD schema forms and formal specification RFC8927.

JSON Schema

Ajv supports most widely used drafts of JSON Schema specification. Please see the informal reference of available JSON Schema validation keywords and specification drafts.

draft-04

Draft-04 is not included in Ajv v7, because of some differences it has with the following drafts:

  • different schema member for schema identifier (id in draft-04 instead of $id)
  • different syntax of exclusiveMaximum/Minimum

You can still use draft-04 schemas with Ajv v6 - while this is no longer actively developed, any security related issues would still be supported at least until 30/06/2021.

To install v6:

bash
npm install ajv@6

You can migrate schemas from draft-04 to draft-07 using ajv-cli.

draft-07 (and draft-06)

These are the most widely used versions of JSON Schema specification, and they are supported with the main ajv export.

javascript
import Ajv from "ajv"
const ajv = new Ajv()

If you need to support draft-06 schemas you need to add additional meta-schema, but you may just change (or remove) $schema attribute in your schemas - no other changes are needed:

javascript
const draft6MetaSchema = require("ajv/dist/refs/json-schema-draft-06.json")
ajv.addMetaSchema(draft6MetaSchema)

draft 2019-09 (and draft-2020-12)

The main advantage of this JSON Schema version over draft-07 is the ability to spread the definition of records that do not allow additional properties across multiple schemas. If you do not need it, you might be better off with draft-07.

To use Ajv with the support of all JSON Schema draft-2019-09/2020-12 features you need to use a different export:

javascript
import Ajv2019 from "ajv/dist/2019"
const ajv = new Ajv2019()

Optionally, you can add draft-07 meta-schema, to use both draft-07 and draft-2019-09 schemas in one Ajv instance:

javascript
const draft7MetaSchema = require("ajv/dist/refs/json-schema-draft-07.json")
ajv.addMetaSchema(draft7MetaSchema)

Draft-2019-09 support is provided via a separate export in order to avoid increasing the bundle and generated code size for draft-07 users.

With this import Ajv supports the following features:

::: warning Draft-2019-09 features performance cost (even when not used)
Supporting dynamic recursive references and unevaluatedProperties/unevaluatedItems keywords adds additional generated code even to the validation functions where these features are not used (when possible, Ajv determines which properties/items are "unevaluated" at compilation time, but support for dynamic references always adds additional generated code). If you are not using these features in your schemas it is recommended to use default Ajv export with JSON-Schema draft-07 support.
:::

Comparison

Both JSON Schema and JSON Type Definition are cross-platform specifications with implementations in multiple programming languages that define the shape of your JSON data.

You can see the difference between the two specifications in Getting started section examples.

This section compares their pros/cons to help decide which specification fits your application better.

JSON Schema

Pros:

  • Wide specification adoption.
  • Used as part of OpenAPI specification.
  • Support of complex validation scenarios:
    • untagged unions and boolean logic
    • conditional schemas and dependencies
    • restrictions on the number ranges and the size of strings, arrays and objects
    • semantic validation with formats, patterns and content keywords
    • distribute strict record definitions across multiple schemas (with unevaluatedProperties)
  • Can be effectively used for validation of any JavaScript objects and configuration files.

Cons:

  • Defines the collection of restrictions on the data, rather than the shape of the data.
  • No standard support for tagged unions.
  • Complex and error prone for the new users (Ajv has strict mode enabled by default to compensate for it, but it is not cross-platform).
  • Some parts of specification are difficult to implement, creating the risk of implementations divergence:
    • reference resolution model
    • unevaluatedProperties/unevaluatedItems
    • dynamic recursive references
  • Internet draft status (rather than RFC)

See JSON Schema for more information and the list of defined keywords.

JSON Type Definition

Pros:

  • Aligned with type systems of many languages - can be used to generate type definitions and efficient parsers and serializers to/from these types.
  • Very simple, enforcing the best practices for cross-platform JSON API modelling.
  • Simple to implement, ensuring consistency across implementations.
  • Defines the shape of JSON data via strictly defined schema forms (rather than the collection of restrictions).
  • Effective support for tagged unions.
  • Designed to protect against user mistakes.
  • Supports compilation of schemas to efficient serializers and parsers (no need to validate as a separate step).
  • Approved as RFC8927.
  • Substantial industry adoption since it was standardized in 2020, Ajv v8.12.0 fixed all reported JTD bugs.

Cons:

  • Limited, compared with JSON Schema - no support for untagged unions1, conditionals, references between different schema files2, etc.
  • No meta-schema in the specification3.

1 Ajv defines non-standard keyword "union" that can be used inside "metadata" object.

2 You can still combine schemas from multiple files in the application code.

3 Ajv defines meta-schema for JTD schemas.

See JSON Type Definition for more information and the list of defined schema forms.


File: docs/guide/typescript.md

Using with TypeScript

[[toc]]

Additional functionality

Ajv takes advantage of TypeScript type system to provide additional functionality that is not possible in JavaScript:

  • utility types JSONSchemaType and JTDSchemaType to convert data type into the schema type to simplify writing schemas, both for JSON Schema (but without union support) and for JSON Type Definition (with tagged unions support).
  • utility type JTDDataType to convert JSON Type Definition schema into the type of data that it defines.
  • compiled validation functions are type guards that narrow the type after successful validation.
  • validation errors for JSON Schema are defined as tagged unions, for type-safe error handling.
  • when utility type is used, compiled JTD serializers only accept data of correct type (as they do not validate that the data is valid) and compiled parsers return correct data type.

Utility types for schemas

For the same example as in Getting started:

  • ensure strictNullChecks is true
```typescript import Ajv, {JSONSchemaType} from "ajv" const ajv = new Ajv()

interface MyData {
foo: number
bar?: string
}

const schema: JSONSchemaType = {
type: "object",
properties: {
foo: {type: "integer"},
bar: {type: "string", nullable: true}
},
required: ["foo"],
additionalProperties: false
}

// validate is a type guard for MyData - type is inferred from schema type
const validate = ajv.compile(schema)

// or, if you did not use type annotation for the schema,
// type parameter can be used to make it type guard:
// const validate = ajv.compile(schema)

const data = {
foo: 1,
bar: "abc"
}

if (validate(data)) {
// data is MyData here
console.log(data.foo)
} else {
console.log(validate.errors)
}

text
</code-block>

<code-block title="JSON Type Definition">
```typescript
import Ajv, {JTDSchemaType} from "ajv/dist/jtd"
const ajv = new Ajv()

interface MyData {
  foo: number
  bar?: string
}

const schema: JTDSchemaType<MyData> = {
  properties: {
    foo: {type: "int32"}
  },
  optionalProperties: {
    bar: {type: "string"}
  }
}


// validate is a type guard for MyData - type is inferred from schema type
const validate = ajv.compile(schema)

// or, if you did not use type annotation for the schema,
// type parameter can be used to make it type guard:
// const validate = ajv.compile<MyData>(schema)

const data = {
  foo: 1,
  bar: "abc"
}

if (validate(data)) {
  // data is MyData here
  console.log(data.foo)
} else {
  console.log(validate.errors)
}

See this test for an advanced example.

Utility type for JTD data type

You can use JTD schema to construct the type of data using utility type JTDDataType

```typescript import Ajv, {JTDDataType} from "ajv/dist/jtd" const ajv = new Ajv()

const schema = {
properties: {
foo: {type: "int32"}
},
optionalProperties: {
bar: {type: "string"}
}
} as const

type MyData = JTDDataType

// type inference is not supported for JTDDataType yet
const validate = ajv.compile(schema)

const validData = {
foo: 1,
bar: "abc"
}

if (validate(validData)) {
// data is MyData here
console.log(validData.foo)
} else {
console.log(validate.errors)
}

text
</code-block>
</code-group>
 

::: warning TypeScript limitation
Note that it's currently not possible for `JTDDataType` to know whether the compiler is inferring timestamps as strings or Dates, and so it conservatively types any timestamp as `string | Date`. This is accurate, but often requires extra validation on the part of the user to confirm they're getting the appropriate data type.
:::

## Type-safe error handling

With both [JSON Schema](../json-schema.md) and [JSON Type Definition](../json-type-definition.md), the validation error type is an open union, but it can be cast to tagged unions (using validation keyword as tag) for easier error handling.

Continuing the example above:

<code-group>
<code-block title="JSON Schema">
```typescript
import {DefinedError} from "ajv"

// ...

if (validate(data)) {
  // data is MyData here
  console.log(data.foo)
} else {
  // The type cast is needed, as Ajv uses a wider type to allow extension
  // You can extend this type to include your error types as needed.
  for (const err of validate.errors as DefinedError[]) {
    switch (err.keyword) {
      case "type":
        // err type is narrowed here to have "type" error params properties
        console.log(err.params.type)
      break
        // ...
    }
  }
}
```typescript import {JTDErrorObject} from "ajv/dist/jtd"

// ...

if (validate(data)) {
// data is MyData here
console.log(data.foo)
} else {
// The type cast is needed, as Ajv uses a wider type to allow extension
// You can extend this type to include your error types as needed.
for (const err of validate.errors as JTDErrorObject[]) {
switch (err.keyword) {
case "type":
// err type is narrowed here to have "type" error params properties
console.log(err.params.type)
break
// ...
}
}
}

text
</code-block>
</code-group>

## Type-safe parsers and serializers

With typescript, your compiled parsers and serializers can be type-safe, either taking their type from schema type or from type parameter passed to compilation functions.

This example uses the same data and schema types as above:

<code-group>
<code-block title="JSON Type Definition">
```typescript
import Ajv, {JTDSchemaType} from "ajv/dist/jtd"
const ajv = new Ajv()

interface MyData {
  foo: number
  bar?: string
}

const schema: JTDSchemaType<MyData> = {
  properties: {
    foo: {type: "int32"}
  },
  optionalProperties: {
    bar: {type: "string"}
  }
}

// serialize will only accept data compatible with MyData
const serialize = ajv.compileSerializer(schema)

// parse will return MyData or undefined
const parse = ajv.compileParser(schema)

// types of parse and serialize are inferred from schema,
// they can also be defined explicitly:
// const parse = ajv.compileParser<MyData>(schema)

const data = {
  foo: 1,
  bar: "abc"
}

const invalidData = {
  unknown: "abc"
}

console.log(serialize(data))
console.log(serialize(invalidData)) // type error

const json = '{"foo": 1, "bar": "abc"}'
const invalidJson = '{"unknown": "abc"}'

console.log(parseAndLogFoo(json)) // logs property
console.log(parseAndLogFoo(invalidJson)) // logs error and position

function parseAndLogFoo(json: string): void {
  const data = parse(json) // MyData | undefined
  if (data === undefined) {
    console.log(parse.message) // error message from the last parse call
    console.log(parse.position) // error position in string
  } else {
    // data is MyData here
    console.log(data.foo)
  }
}

Type-safe unions

JSON Type Definition only supports tagged unions, so unions in JTD are fully supported for JTDSchemaType and JTDDataType.
JSON Schema is more complex and so JSONSchemaType has limited support for type safe unions.

JSONSchemaType will type check unions where each union element is fully specified as an element of an anyOf array or oneOf array.
Additionally, unions of primitives will type check appropriately if they're combined into an array type, e.g. {type: ["string", "number"]}.

::: warning TypeScript limitation
Note that due to current limitation of TypeScript, JSONSchemaType cannot verify that every element of the union is present, and the following example is still valid const schema: JSONSchemaType<number | string> = {type: "string"}.
:::

Here's a more detailed example showing several union types:

```typescript import Ajv, {JSONSchemaType} from "ajv" const ajv = new Ajv()

type MyUnion = {prop: boolean} | string | number

const schema: JSONSchemaType = {
anyOf: [
{
type: "object",
properties: { prop: { type: "boolean" } },
required: ["prop"],
},
{
type: ["string", "number"]
}
]
}

text
</code-block>
</code-group>