ajv-validator/ajv

Nullable doesn't work on oneOf fields

person1123 opened this issue · 4 comments

What version of Ajv are you using? Does the issue happen if you use the latest version?
8.11.0, Also reproduces on 8.12.0

Ajv options object

{allErrors: true}, {useDefaults: true}

JSON Schema

Schema 1 (doesn't work)

{
  "type": "object",
  "properties": {
    "foo": {
      "type": "object",
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "foo": {
              "type": "string"
            }
          },
          "required": [
            "foo"
          ]
        },
        {
          "type": "object",
          "properties": {
            "bar": {
              "type": "number",
              "maximum": 3
            }
          },
          "required": [
            "bar"
          ]
        }
      ],
      "nullable": true
    }
  },
  "required": [
    "foo"
  ]
}¡

Schema 2 (works)

{
  "type": "object",
  "properties": {
    "foo": {
      "type": "object",
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "foo": {
              "type": "string"
            }
          },
          "required": [
            "foo"
          ]
        },
        {
          "type": "object",
          "properties": {
            "bar": {
              "type": "number",
              "maximum": 3
            }
          },
          "required": [
            "bar"
          ]
        },
        {
          "type": "null"
        }
      ],
      "nullable": true
    }
  },
  "required": [
    "foo"
  ]
}

Sample data

{"foo": null}

Your code

Runkit: https://runkit.com/embed/f2r6epfwc8h7

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

const objectASchema = {
  type: "object",
  properties: {
    foo: {type: "string"},
  },
  required: ['foo']
}

const objectBSchema = {
  type: "object",
  properties: {
    bar: {type: "number", maximum: 3},
  },
  required: ['bar']
}

const schema = {
  type: "object",
  properties: {
    foo: {
      type: "object",
      oneOf: [objectASchema, objectBSchema],
      nullable: true
    }
  },
  required: ['foo']
}

const schema2 = {
  type: "object",
  properties: {
    foo: {
      type: "object",
      oneOf: [objectASchema, objectBSchema, {type: 'null'}],
      nullable: true
    }
  },
  required: ['foo']
}

const validate = ajv.compile(schema)
const validate2 = ajv.compile(schema2)

test({foo: {foo: '5'}})
test({foo: {bar: 2}})
test({foo: null})

test2({foo: {foo: '5'}})
test2({foo: {bar: 2}})
test2({foo: null})


function test(data) {
  console.log("Testing",data);
  const valid = validate(data)
  if (valid) console.log("Valid!")
  else console.log("Invalid: " + ajv.errorsText(validate.errors))
}

function test2(data) {
  console.log("Testing with schema 2",data);
  const valid = validate2(data)
  if (valid) console.log("Valid!")
  else console.log("Invalid: " + ajv.errorsText(validate.errors))
}

Validation result, data AFTER validation, error messages

Testing
Object {foo: Object {foo: "5"}}
Valid!
Testing
Object {foo: Object {bar: 2}}
Valid!
**Testing
Object {foo: null}
Invalid: data/foo must be object, data/foo must be object, data/foo must match exactly one schema in oneOf**
Testing with schema 2
Object {foo: Object {foo: "5"}}
Valid!
Testing with schema 2
Object {foo: Object {bar: 2}}
Valid!
**Testing with schema 2
Object {foo: null}
Valid!**

What results did you expect?
I would expect that since in both schemas the foo field is marked as nullable, {foo: null} should pass validation. However, it only passes validation where an explicit null option is included in the oneOf

Are you going to resolve the issue?
I can use the second version of the schema as a workaround for now.

oneOf docs are very clear, your data MUST match EXACTLY one of the schemas in the array. In your schema 1 which "doesn't work" the data doesn't match one of the schemas, so for me it appears to be behaving as expected.

I personally think the docs aren't clear at all, and it looks like even within the broader OpenAPI community there was a lot of disagreement about the use of nullable as it relates to json schema.

They did however firmly land in the "nullable doesn't override oneOf" camp.

https://github.com/OAI/OpenAPI-Specification/blob/main/proposals/2019-10-31-Clarify-Nullable.md

It wouldn't be the worst thing to update the ajv docs for oneOf/anyof/allof to mention this very unintuitive interaction pattern

Thanks for the extra info @theahura and for sharing your thoughts. I'm happy to accept an update to the docs. I can do it myself but if someone else submits the PR I can actually approve and merge it whereas if I submit it myself I can't :D

Closing as this has been shown to be the expected behaviour, but will explore a change to the docs, by myself or someone else.