Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

discriminator keyword, closes #1119 #1494

Merged
merged 3 commits into from
Mar 16, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
docs: discriminator
  • Loading branch information
epoberezkin committed Mar 16, 2021
commit 29a5b70feebb1573be3a546c223d64cab6b37889
40 changes: 38 additions & 2 deletions docs/guide/modifying-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ If `removeAdditional` option in the example above were `"all"` then both `additi
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
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](../json-schema.md#discriminator) keyword (requires `discriminator` option).
:::

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

```javascript
{
Expand Down Expand Up @@ -120,6 +120,38 @@ While this behaviour is unexpected (issues [#129](https://github.com/ajv-validat

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

For discriminated unions you could schemas with [discriminator](../json-schema.md#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](../json-schema.md#discriminator) keyword.

## Assigning defaults

With [option `useDefaults`](./api.md#options) 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.
Expand Down Expand Up @@ -180,6 +212,10 @@ The strict mode option can change the behaviour for these unsupported defaults (

See [Strict mode](./strict-mode.md).

::: tip Default with discriminator keyword
Defaults will be assigned in schemas inside `oneOf` in case [discriminator](../json-schema.md#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.
Expand Down
96 changes: 95 additions & 1 deletion docs/json-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,15 @@ v7 added support for all new keywords in draft-2019-09:

There is also support for [$dynamicAnchor/$dynamicRef](./guide/combining-schemas.md#extending-recursive-schemas) from the next version of JSON Schema draft that will replace `$recursiveAnchor`/`$recursiveRef`.

## `type`
## OpenAPI support

Ajv supports these additional [OpenAPI specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md) keywords:
- [nullable](#nullable) - to avoid using `type` keyword with array of types.
- [discriminator](#discriminator) - to optimize validation and error reporting of tagged unions

## JSON data type

### `type`

`type` keyword requires that the data is of certain type (or some of types). Its value can be a string (the allowed type) or an array of strings (multiple allowed types).

Expand Down Expand Up @@ -51,6 +59,33 @@ Most other keywords apply only to a particular type of data. If the data is of d

In v7 Ajv introduced [Strict types](./strict-mode.md#strict-types) mode that makes these mistakes less likely by requiring that types are constrained with type keyword whenever another keyword that applies to specific type is used.

### nullable <Badge text="OpenAPI" />

This keyword can be used to allow `null` value in addition to the defined `type`.

Ajv supports it by default, without additional options. These two schemas are equivalent, but the first one is better supported by some tools and is also compatible with `strict.types` option (see [Strict types](./strict-mode.md#strict-types))

```json
{
"type": "string",
"nullable": true
}
```

and

```json
{
"type": ["string", "null"]
}
```

::: warning nullable does not extend enum and const
If you use [enum](#enum) or [const](#const) keywords, `"nullable": true` would not extend the list of allowed values - `null` value has to be explicitly added to `enum` (and `const` would fail, unless it is `"const": null`)

This is different from how `nullable` is defined in [JSON Type Definition](./json-type-definition.md), where `"nullable": true` allows `null` value in addition to any data defined by the schema.
:::

## Keywords for numbers

### `maximum` / `minimum` and `exclusiveMaximum` / `exclusiveMinimum`
Expand Down Expand Up @@ -674,6 +709,65 @@ _invalid_:

See [tests](https://github.com/json-schema-org/JSON-Schema-Test-Suite/blob/master/tests/draft2019-09/unevaluatedProperties.json) for `unevaluatedProperties` keyword for other examples.

### discriminator <Badge text="NEW: OpenAPI" />

Ajv has a limited support for `discriminator` keyword: to optimize validation, error handling, and [modifying data](./guide/modifying-data.md) with [oneOf](#oneof) keyword.

Its value should be an object with a property `propertyName` - the name of the property used to discriminate between union members.

When using discriminator keyword only one subschema in `oneOf` will be used, determined by the value of discriminator property.

::: warning Use option discriminator
To use `discriminator` keyword you have to use option `discriminator: true` with Ajv constructor - it is not enabled by default.
:::

**Example**

_schema_:

```javascript
{
type: "object",
discriminator: {propertyName: "foo"},
required: ["foo"],
oneOf: [
{
properties: {
foo: {const: "x"},
a: {type: "string"},
},
required: ["a"],
},
{
properties: {
foo: {enum: ["y", "z"]},
b: {type: "string"},
},
required: ["b"],
},
],
}
```

_valid_: `{foo: "x", a: "any"}`, `{foo: "y", b: "any"}`, `{foo: "z", b: "any"}`

_invalid_:

- `{}`, `{foo: 1}` - discriminator tag must be string
- `{foo: "bar"}` - discriminator tag value must be in oneOf subschema
- `{foo: "x", b: "b"}`, `{foo: "y", a: "a"}` - invalid object

From the perspective of validation result `discriminator` is defined as no-op (that is, removing discriminator will not change the validity of the data), but errors reported in case of invalid data will be different.

There are following requirements and limitations of using `discriminator` keyword:
- `mapping` in discriminator object is not supported.
- [oneOf](#oneof) keyword must be present in the same schema.
- discriminator property should be [requried](#required) either on the top level, as in the example, or in all `oneOf` subschemas.
- each `oneOf` subschema must have [properties](#properties) keyword with discriminator property.
- schema for discriminator property in each `oneOf` subschema must be [const](#const) or [enum](#enum), with unique values across all subschemas.

Not meeting any of these requirements would fail schema compilation.

## Keywords for all types

### `enum`
Expand Down
5 changes: 5 additions & 0 deletions docs/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const defaultOptions = {
$data: false, // *
allErrors: false,
verbose: false,
discriminator: false, // *
$comment: false, // *
formats: {},
keywords: {},
Expand Down Expand Up @@ -123,6 +124,10 @@ Check all rules collecting all errors. Default is to return after the first erro

Include the reference to the part of the schema (`schema` and `parentSchema`) and validated data in errors (false by default).

### discriminator

Support [discriminator keyword](./json-schema.md#discriminator) from [OpenAPI specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.1.0.md).

### $comment

Log or pass the value of `$comment` keyword to a function.
Expand Down