question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

OneOf validation issue when schema has all fields optionals (not required)

See original GitHub issue

What is going on?

AJV has oneOf keyword. Validators have to validate data against all schemas to establish validity according to this keyword.

I have validation schema that has one field (field ‘json’) which may be one of two schemas (schema ‘A’ and schema ‘B’). This two schemas have different fields and all of these fields are optional (not required).

const schema = {
  type: 'object',
  properties: {
    json: {
      type: 'object',
      oneOf: [
        {
          title: 'A',
          type: 'object',
          properties: {
            propA: {
              type: 'number',
            }
          },
        },
        {
          title: 'B',
          type: 'object',
          properties: {
            propB: {
              type: 'number'
            }
          },
        }
      ]
    }
  },
  required: ['json']
};
const data = {
  json: {
     propA: 123,
  },
};

When I’m trying to validate object with this schema where I have field ‘json’ with type of one of the validation schemas (schema ‘A’ or schema ‘B’) validator says data.json should match exactly one schema in oneOf.

You can minimal repository with all tests in index.js. Start script with npm run start.

All minimal repo content

Code

const Ajv = require("ajv");
const ajv = new Ajv({allErrors: true});

function test(data) {
  const valid = validate(data)
  if (valid) console.log("Valid!\n")
  else console.log(`Invalid: ${ajv.errorsText(validate.errors)}\n`)
}

const schema = {
  type: 'object',
  properties: {
    json: {
      type: 'object',
      oneOf: [
        {
          title: 'A',
          type: 'object',
          properties: {
            propA: {
              type: 'number',
            }
          }
        },
        {
          title: 'B',
          type: 'object',
          properties: {
            propB: {
              type: 'number'
            }
          },
        }
      ]
    }
  }
};

const validate = ajv.compile(schema)

const data = {
  json: {},
};

data.json.propA = 123;
console.log(`1: Should be VALID (because it fits object 'A') but it is INVALID => INCORRECT VALIDATION`);
test(data);

data.json.propA = '123';

console.log(`2: Should be INVALID (because of 'propA' must be number) but it's VALID => INCORRECT VALIDATION`);
test(data);

delete data.json.propA;
data.json.propB = 123;

console.log(`3: Should be VALID (because it fits object 'B') but it is INVALID => INCORRECT VALIDATION`);
test(data);

data.json.propB = '123';

console.log(`4: Should be INVALID (because of 'propA' must be number) but it's VALID => INCORRECT VALIDATION`);
test(data);

schema.properties.json.oneOf[0].required = ['propA'];
delete data.json.propB;

console.log(`5: Should be VALID (because it fits object 'B' -> 'propB' optional) but it is INVALID => INCORRECT VALIDATION`);
test(data);

console.log(`6: Should be VALID (because it fits object 'A' -> 'propA' number) but it is INVALID => INCORRECT VALIDATION`);
data.json.propA = 123;
test(data);

console.log(`7: Should be INVALID (because it doesn't fit object 'A' -> 'propA' must be number) but it's VALID => INCORRECT VALIDATION`);
data.json.propA = '123';
test(data);

delete data.json.propA;

console.log(`9: Should be VALID (because it fits object 'B' -> 'propB' number) but it's INVALID => INCORRECT VALIDATION`);
data.json.propB = 123;
test(data);

console.log(`10: Should be INVALID (because it doesn't fit object 'B' -> 'propB' must be number) but it's VALID => INCORRECT VALIDATION`);
data.json.propB = '123';
test(data);

delete schema.properties.json.oneOf[0].required;
schema.properties.json.oneOf[1].required = ['propB'];
console.log(`11: Should be VALID (because it fits object 'A' -> 'propA' optional) and it's VALID => CORRECT VALIDATION`);
test(data);

console.log(`12: Should be VALID (because it fits object 'A' -> 'propA' number) and it's VALID => CORRECT VALIDATION`);
data.json.propA = 123;
test(data);

console.log(`13: Should be INVALID (because it doesn't fit object 'B' -> 'propB' must be number) and it's INVALID => CORRECT VALIDATION`);
data.json.propA = '123';
test(data);

delete data.json.propA;

console.log(`14: Should be VALID (because it fits object 'B' -> 'propB' number) but it's INVALID => INCORRECT VALIDATION`);
data.json.propB = 123;
test(data);

console.log(`15: Should be INVALID (because it doesn't fit object 'B' -> 'propB' must be number) but it's VALID => INCORRECT VALIDATION`);
data.json.propB = '123';
test(data);

Output

1: Should be VALID (because it fits object 'A') but it is INVALID => INCORRECT VALIDATION
Invalid: data/json must match exactly one schema in oneOf

2: Should be INVALID (because of 'propA' must be number) but it's VALID => INCORRECT VALIDATION
Valid!

3: Should be VALID (because it fits object 'B') but it is INVALID => INCORRECT VALIDATION
Invalid: data/json must match exactly one schema in oneOf

4: Should be INVALID (because of 'propA' must be number) but it's VALID => INCORRECT VALIDATION
Valid!

5: Should be VALID (because it fits object 'B' -> 'propB' optional) but it is INVALID => INCORRECT VALIDATION
Invalid: data/json must match exactly one schema in oneOf

6: Should be VALID (because it fits object 'A' -> 'propA' number) but it is INVALID => INCORRECT VALIDATION
Invalid: data/json must match exactly one schema in oneOf

7: Should be INVALID (because it doesn't fit object 'A' -> 'propA' must be number) but it's VALID => INCORRECT VALIDATION
Valid!

9: Should be VALID (because it fits object 'B' -> 'propB' number) but it's INVALID => INCORRECT VALIDATION
Invalid: data/json must match exactly one schema in oneOf

10: Should be INVALID (because it doesn't fit object 'B' -> 'propB' must be number) but it's VALID => INCORRECT VALIDATION
Valid!

11: Should be VALID (because it fits object 'A' -> 'propA' optional) and it's VALID => CORRECT VALIDATION
Valid!

12: Should be VALID (because it fits object 'A' -> 'propA' number) and it's VALID => CORRECT VALIDATION
Valid!

13: Should be INVALID (because it doesn't fit object 'B' -> 'propB' must be number) and it's INVALID => CORRECT VALIDATION
Invalid: data/json/propA must be number, data/json/propB must be number, data/json must match exactly one schema in oneOf

14: Should be VALID (because it fits object 'B' -> 'propB' number) but it's INVALID => INCORRECT VALIDATION
Invalid: data/json must match exactly one schema in oneOf

15: Should be INVALID (because it doesn't fit object 'B' -> 'propB' must be number) but it's VALID => INCORRECT VALIDATION
Valid!

Default issue template

What version of Ajv are you using? Does the issue happen if you use the latest version? 8.6.3, Yes.

Ajv options object

const Ajv = require("ajv");
const ajv = new Ajv({allErrors: true});

JSON Schema

{
   "type":"object",
   "properties":{
      "json":{
         "type":"object",
         "oneOf":[
            {
               "title":"A",
               "type":"object",
               "properties":{
                  "propA":{
                     "type":"number"
                  }
               }
            },
            {
               "title":"B",
               "type":"object",
               "properties":{
                  "propB":{
                     "type":"number"
                  }
               }
            }
         ]
      }
   }
}

Sample data

{
   "json":{
      "fieldA":123
   }
}

Your code

const validate = ajv.compile(schema)
const valid = validate(data);
if (valid) console.log("Valid!\n");
else console.log(`Invalid: ${ajv.errorsText(validate.errors)}\n`)

Validation result, data AFTER validation, error messages

Invalid: data/json must match exactly one schema in oneOf

What results did you expect? Should be VALID (because it fits object ‘A’)

My Environment

Dependency Version
Operating System MacOS Big Sur 11.6
Node.js version 14.17.3
NPM version 4.4.3
AJV version 8.6.3

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:10 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
epoberezkincommented, Oct 20, 2021

but why validator returns that this object is not valid?

sorry misread your comment. Because it is valid against both subschemas, it is valid against the whole schema - that’s how oneOf is defined - it should be valid against exactly one subschema to be valid against oneOf - it’s exclusive OR operation

0reactions
crizo23commented, Oct 21, 2021

@epoberezkin thanks. I suppose “not supported” falls under this clause in the docs

Ajv has a limited support for discriminator

Curious, is it not supported as “discriminator” is more of an OpenAPI thing?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Schema Validation: Multiple optional fields, but require at ...
Inserting a custom check in a separate required field, which validates the presence of at least one of the two optional fields, and...
Read more >
JSON Schema oneof without required
[EDITED - SEE THIRD SOLUTION BELOW]. Solution 1 - At least one, but not both (verbose). I would add an allOf with a...
Read more >
Validation middlewares - express-validator
If any of the fields are present in more than one location, then all instances of that field value must pass the validation....
Read more >
oneOf, anyOf, allOf, not
You can use it to validate the request body contains all the necessary information about the object to be updated, depending on the...
Read more >
AllOf, AnyOf, OneOf
OneOf : One (and only one) of the contained schemas must validate against the instance value. The AllOf, AnyOf, OneOf nodes are all...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found