ajv: removeAdditional doesn't work well with anyOf, oneOf or allOf

Hi, first of all, awesome work with this validator! Very handy!

Now here is the problem I’m facing, I’m not sure if this is the expected behavior, but if it is, it’s not very intuitive… my scenario is a bit more complex, but I’ll simplify in the following example.

I’m initializing ajv with:

const ajv = Ajv({removeAdditional: true})

This is the sample schema:

const schema = {
    type: `object`,
    additionalProperties: {
      anyOf: [{
        type: `object`,
        properties: {
          a: {
            type: `string`
          }
        },
        required: [`a`],
        additionalProperties: false
      }, {
        type: `object`,
        properties: {
          b: {
            type: `string`
          }
        },
        required: [`b`],
        additionalProperties: false
      }]
    }
  }

It should accept an object with as many objects as we want, and each of these objects should have at least one property called a, or one called b. If a or b are not present, it should fail. It should also discard any other property that is not a or b.

Now, with this data:

  const data1 = {
    obj1: {
      a: `a`
    },
    obj2: {
      b: `b`
    },
    obj3: {
      c: `c`
    }
  }

It should fail only on obj3, but that’s only the case when removeAdditional is false. If removeAdditional is true, it seems like as soon as it evaluates the first option of the anyOf, it removes the additional properties of the object before trying the second option.

the removeAdditional is very useful, but it needs to wait until all possibilities of oneOf to be evaluated before removing anything.

This is the error I get by running the code above:

     Error: the array [
  {
    "dataPath": "['obj2']"
    "keyword": "required"
    "message": "should have required property 'a'"
    "params": {
      "missingProperty": "a"
    }
    "schemaPath": "#/additionalProperties/anyOf/0/required"
  }
  {
    "dataPath": "['obj2']"
    "keyword": "required"
    "message": "should have required property 'b'"
    "params": {
      "missingProperty": "b"
    }
    "schemaPath": "#/additionalProperties/anyOf/1/required"
  }
  {
    "dataPath": "['obj2']"
    "keyword": "anyOf"
    "message": "should match some schema in anyOf"
    "params": {}
    "schemaPath": "#/additionalProperties/anyOf"
  }
  {
    "dataPath": "['obj3']"
    "keyword": "required"
    "message": "should have required property 'a'"
    "params": {
      "missingProperty": "a"
    }
    "schemaPath": "#/additionalProperties/anyOf/0/required"
  }
  {
    "dataPath": "['obj3']"
    "keyword": "required"
    "message": "should have required property 'b'"
    "params": {
      "missingProperty": "b"
    }
    "schemaPath": "#/additionalProperties/anyOf/1/required"
  }
  {
    "dataPath": "['obj3']"
    "keyword": "anyOf"
    "message": "should match some schema in anyOf"
    "params": {}
    "schemaPath": "#/additionalProperties/anyOf"
  }
] was thrown, throw an Error :)

As you can see, obj2 is failing because it’s missing property b, which is not true.

I called validate with

const result = ajv.validate(schema, data1)

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 2
  • Comments: 15 (10 by maintainers)

Commits related to this issue

Most upvoted comments

@epoberezkin

Firstly, thank you for AJV and the continual support you provide for AJV and for the JSON-schema standard in general. We use AJV at Wix (you can write this in your README!).

I read both this thread and #134, and I must say, I still disagree that this behavior makes sense. While the JSON-schema spec does specify that validation for sub-schemas are only for the sub-schema, the ‘removeAdditional’ behavior is not standard and does not need to abide by this.

Furthermore, the compound anyOf/allOf/oneOf keywords specifically modify the validation result of the (parent) schema, and it would make more sense to validate the parent schema, and then to remove additional properties by using the ‘correct path’ of the validated schema.

Of course the current implementation does not have this notion, but consider for a moment the annotations feature in the JSON schema specification: https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.7.7

Let’s assume for a moment that we implemented the non-standard removeAdditional as an annotation on the schemas. Now this behavior is defined as an annotation on the schema which different validators can implement.

In this instance, I would fully expect the annotation to be resolved according to JSON-schema specification - namely, that the ‘valid’ instance from anyOf would collect the annotation + JSON pointer from the valid subschema, as specified here: https://json-schema.org/draft/2019-09/json-schema-core.html#rfc.section.9.2.1.2

This feature would still be working in a standard fashion according to how JSON-schema rules apply, but in this case it would also be aligned with the myriad developers who thought this is how it should work to begin with.

Basically, the fact that the implementation of non-standard removeAdditional is aligned with the standard validation specification, does not mean it is expected nor correct. We can easily align it with another standard specification, whether or not the actual implementation uses that feature (annotations - which it could have under the hood, by modifying schemas as they are added, for example)

What do you think? I would be happy to contribute towards a working solution, if my argument is convincing 😃

now that it’s used by lots of people it would be more confusing if it’s changed.

The setting already has 4 different modes. I’m sure the existing users wouldn’t care if a fifth mode was added.

@epoberezkin would you accept a PR that removed the additional properties only after passing validation of the schema? That way, if a subschema fails validation for some other reason, the properties aren’t removed.

Something like removeAdditional: 'afterValidation'? I think this would be more intuitive in these cases.