Skip to content

JSONSchemaType + anyOf corresponding to union over objects cannot correctly determine missing fields #144

Open
@alexandervandekleutab

Description

Problem description

AJV cannot infer which subtype of a union is being used when creating a union over objects. This might be an issue with AJV core, or just an issue with the error handling.

Below is a simple but complete example of the issue:

import ajvErrors from 'ajv-errors'
import addFormats from 'ajv-formats'
import Ajv, { JSONSchemaType } from 'ajv'

type UploadAction = {
  actionType: 'UPLOAD'
  url: string
  filename: string
}

type EmailAction = {
  actionType: 'EMAIL'
  email: string
}

type Action = UploadAction | EmailAction
type Actions = Array<Action>

const schema: JSONSchemaType<Actions> = {
  type: 'array',
  items: {
    anyOf: [
      {
        type: 'object',
        required: ['actionType', 'url', 'filename'],
        properties: {
          actionType: {
            type: 'string',
            const: 'UPLOAD',
          },
          url: {
            type: 'string',
          },
          filename: {
            type: 'string',
          },
        },
      },
      {
        type: 'object',
        required: ['actionType', 'email'],
        properties: {
          actionType: {
            type: 'string',
            const: 'EMAIL',
          },
          email: {
            type: 'string',
          },
        },
      },
    ],
  },
}

if (require.main === module) {
  const ajv = new Ajv({ allErrors: true })
  ajvErrors(ajv)
  addFormats(ajv)
  const validate = ajv.compile(schema)

  const data: Array<unknown> = [
    {
      actionType: 'UPLOAD',
    },
  ]

  if (validate(data)) {
    console.log(data)
  } else {
    console.log(validate.errors)
  }
}

Run this using ts-node or compile and run it via node. The output I get is

[
  {
    instancePath: '/0',
    schemaPath: '#/items/anyOf/0/required',
    keyword: 'required',
    params: { missingProperty: 'url' },
    message: "must have required property 'url'"
  },
  {
    instancePath: '/0',
    schemaPath: '#/items/anyOf/0/required',
    keyword: 'required',
    params: { missingProperty: 'filename' },
    message: "must have required property 'filename'"
  },
  {
    instancePath: '/0',
    schemaPath: '#/items/anyOf/1/required',
    keyword: 'required',
    params: { missingProperty: 'email' },
    message: "must have required property 'email'"
  },
  {
    instancePath: '/0/actionType',
    schemaPath: '#/items/anyOf/1/properties/actionType/const',
    keyword: 'const',
    params: { allowedValue: 'EMAIL' },
    message: 'must be equal to constant'
  },
  {
    instancePath: '/0',
    schemaPath: '#/items/anyOf',
    keyword: 'anyOf',
    params: {},
    message: 'must match a schema in anyOf'
  }
]

When I specify actionType: 'UPLOAD', I expect that the only missing fields should url and filename. The errors output by AJV are confusing and misleading.

In contrast, the typescript type inference engine is correctly able to deduce the the missing fields once the actionType field is set to one of EMAIL or UPLOAD.

Here are my versions via yarn list:

├─ [email protected]
├─ [email protected]
│  └─ ajv@^8.0.0
├─ [email protected]
│  ├─ fast-deep-equal@^3.1.1
│  ├─ json-schema-traverse@^1.0.0
│  ├─ require-from-string@^2.0.2
│  └─ uri-js@^4.2.2

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions