7

Assuming I have the schema & JSON:

JSON Schema:

const schema = {
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "required": [ "countries" ],
  "definitions": {
    "europeDef": {
      "type": "object",
      "required": ["type"],
      "properties": { "type": {"const": "europe"} }
    },
    "asiaDef": {
      "type": "object",
      "required": ["type"],
      "properties": { "type": {"const": "asia"} }
    }
  },
  "properties": {
    "countries": {
      "type": "array",
      "items": {
        "oneOf":[
          { "$ref": "#/definitions/europeDef" },
          { "$ref": "#/definitions/asiaDef"}
        ]
      }
    }
  }
}

JSON:

const data = {
  "countries":[
    {"type": "asia1"},
    {"type": "europe1"}
  ]
}
const isValid = ajv.validate(schema, data); //schema, data
if(! isValid){
  console.log(ajv.errors);
}

and the error message:

[ { keyword: 'const',
    dataPath: '/countries/0/type',
    schemaPath: '#/definitions/europeDef/properties/type/const',
    params: { allowedValue: 'europe' },
    message: 'should be equal to constant' },
  { keyword: 'const',
    dataPath: '/countries/0/type',
    schemaPath: '#/definitions/asiaDef/properties/type/const',
    params: { allowedValue: 'asia' },
    message: 'should be equal to constant' },
  { keyword: 'oneOf',
    dataPath: '/countries/0',
    schemaPath: '#/properties/countries/items/oneOf',
    params: { passingSchemas: null },
    message: 'should match exactly one schema in oneOf' },
  { keyword: 'const',
    dataPath: '/countries/1/type',
    schemaPath: '#/definitions/europeDef/properties/type/const',
    params: { allowedValue: 'europe' },
    message: 'should be equal to constant' },
  { keyword: 'const',
    dataPath: '/countries/1/type',
    schemaPath: '#/definitions/asiaDef/properties/type/const',
    params: { allowedValue: 'asia' },
    message: 'should be equal to constant' },
  { keyword: 'oneOf',
    dataPath: '/countries/1',
    schemaPath: '#/properties/countries/items/oneOf',
    params: { passingSchemas: null },
    message: 'should match exactly one schema in oneOf' } ]

My question is, as I have derived this schema so I can pretty much understand the error. But for a third person it would definitely take some time to figure it out (and it may take more time, if the schema/errors are more complex).

Any way to make it more user-friendly?

user8479984
  • 451
  • 2
  • 9
  • 23
  • Does this answer your question? [best way to make conditional arrays in json schema with decent error messages](https://stackoverflow.com/questions/59976752/best-way-to-make-conditional-arrays-in-json-schema-with-decent-error-messages) – Jason Desrosiers Apr 03 '20 at 21:26
  • @JasonDesrosiers This is a sample but in reality my json schema is more complex and comprise of if-then, enum, oneOf, allOf etc. and having the order in that sequence probaly not possible all the time. Also, we cannot present the above error to the end-user as a reponse as it will be more complex to understand and non-user friendly. Any alternatives ? – user8479984 Apr 04 '20 at 13:43
  • When using `formik`, with ajv as the resolver for validation, it outputs a `errors` object, which is the same shape as your form data object, but instead of the form values it has an error object with `message` etc. Anyone know a quick way to convert the ajv errors output to something like this? I essentially just want to match the error to the property in the form. – conor909 May 06 '22 at 09:40

3 Answers3

6

If you require it for API then @apideck/better-ajv-errors is simple and outputs more readable error message.

Here is an example:

import Ajv from 'ajv';
import { betterAjvErrors } from '@apideck/better-ajv-errors';

// Without allErrors: true, ajv will only return the first error
const ajv = new Ajv({ allErrors: true });

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

const data = {
  foo: "I don't know integer",
  bar: "abc"
}

const validate = ajv.compile(schema)
const valid = validate(data)

if (!valid) {
  console.log('Errors from ajv:\n', JSON.stringify(validate.errors, null, 2));
  const betterErrors = betterAjvErrors({ schema, data, errors: validate.errors });
  console.log('Errors from betterAjvErrors:\n', JSON.stringify(betterErrors, null, 2));
}

Here is output:

Errors from ajv:
 [
  {
    "instancePath": "/foo",
    "schemaPath": "#/properties/foo/type",
    "keyword": "type",
    "params": {
      "type": "integer"
    },
    "message": "must be integer"
  }
]
Errors from betterAjvErrors:
 [
  {
    "message": "'foo' property type must be integer",
    "path": "{base}.foo",
    "context": {
      "errorType": "type"
    }
  }
]

Gagan
  • 1,267
  • 3
  • 18
  • 28
4

You can customize the messages using ajv-errors extension that let you write better messages (or keys to apply the I18N logic).

It will show what you set in errorMessage

const Ajv = require('ajv')
const AjvErrors = require('ajv-errors')

const ajv = new Ajv({ allErrors: true, jsonPointers: true })
AjvErrors(ajv)

const isValid = ajv.validate(yourSchema(), {
  countries: [
    { type: 'asia1' },
    { type: 'europe1' }
  ]
})
console.log(isValid)

if (!isValid) {
  console.log(ajv.errors)
}
function yourSchema () {
  return {
    $schema: 'http://json-schema.org/draft-07/schema#',
    type: 'object',
    required: ['countries'],
    definitions: {
      europeDef: {
        type: 'object',
        required: ['type'],
        properties: { type: { const: 'europe' } },
        errorMessage: 'it must be europe'
      },
      asiaDef: {
        type: 'object',
        required: ['type'],
        properties: { type: { const: 'asia' } },
        errorMessage: 'it must be asia'
      }
    },
    properties: {
      countries: {
        type: 'array',
        errorMessage: 'should be one of asia or europe',
        items: {
          oneOf: [
            { $ref: '#/definitions/europeDef' },
            { $ref: '#/definitions/asiaDef' }
          ]
        }
      }
    }
  }
}
Manuel Spigolon
  • 11,003
  • 5
  • 50
  • 73
4

See also additional library better-ajv-errors to show detailed error messages with context. The library requires to re-process schema and data:

const valid = validate(data);

if (!valid) {
  const output = betterAjvErrors(schema, data, validate.errors);
  console.log(output);
}

enter image description here

Jakob
  • 3,570
  • 3
  • 36
  • 49