Mongo Json Schema Validator AnyOf not working

2020-04-17 07:22发布

问题:

I have created the a collection with the below validation:

{
  $jsonSchema: {
    bsonType: 'object',
    additionalProperties: false,
    properties: {
      _id: {
        bsonType: 'objectId'
      },
      test: {
        bsonType: 'string'
      }
    },
    anyOf: [
      {
        bsonType: 'object',
        properties: {
          test1: {
            bsonType: 'string'
          }
        },
        additionalProperties: false
      },
      {
        bsonType: 'object',
        properties: {
          test2: {
            bsonType: 'string'
          }
        },
        additionalProperties: false
      }
    ]
  }
}

The Validation Action was set to Error and Validation Level to Moderate.

When I try to insert a document containing the field test and test1, I am getting a validation error

Document:
db.dmt9.insert([{test:"123"},{test1:"456"}])

Error:

BulkWriteResult({
        "writeErrors" : [
                {
                        "index" : 0,
                        "code" : 121,
                        "errmsg" : "Document failed validation",
                        "op" : {
                                "_id" : ObjectId("5dd511de1f7b2047ed527044"),
                                "test" : "123"
                        }
                }
        ],
        "writeConcernErrors" : [ ],
        "nInserted" : 0,
        "nUpserted" : 0,
        "nMatched" : 0,
        "nModified" : 0,
        "nRemoved" : 0,
        "upserted" : [ ]
})

Since I am inserting a document containing one of the fields inside the AnyOf, shouldn't the document be inserted successfully? If I try and change the Validation Action to Warning, the document is inserted however I can insert any other field.

Test with validation Action - Warning

rs0:PRIMARY> db.dmt9.insert([{test:"123"},{test1:"456"},{test9:123}])
BulkWriteResult({
        "writeErrors" : [ ],
        "writeConcernErrors" : [ ],
        "nInserted" : 3,
        "nUpserted" : 0,
        "nMatched" : 0,
        "nModified" : 0,
        "nRemoved" : 0,
        "upserted" : [ ]
})

Update 2: When I remove the field test I get this validator:

{
  $jsonSchema: {
    bsonType: 'object',
    additionalProperties: false,
    anyOf: [
      {
        bsonType: 'object',
        properties: {
          test1: {
            bsonType: 'string'
          }
        },
        additionalProperties: false
      },
      {
        bsonType: 'object',
        properties: {
          test2: {
            bsonType: 'string'
          }
        },
        additionalProperties: false
      }
    ],
    properties: {
      _id: {
        bsonType: 'objectId'
      }
    }
  }
}

and when i try to insert test1 again I still get the error message Document failed validation

rs0:PRIMARY> db.dmt8.insert([{test1:"123"}])
BulkWriteResult({
        "writeErrors" : [
                {
                        "index" : 0,
                        "code" : 121,
                        "errmsg" : "Document failed validation",
                        "op" : {
                                "_id" : ObjectId("5dd51b4766ba25a01fbcf8e6"),
                                "test1" : "123"
                        }
                }
        ],
        "writeConcernErrors" : [ ],
        "nInserted" : 0,
        "nUpserted" : 0,
        "nMatched" : 0,
        "nModified" : 0,
        "nRemoved" : 0,
        "upserted" : [ ]
})

回答1:

{test:"123"} fails validation because it doesn't conform to any of the schemas in anyOf, which need test1 or test2 as the only key.

anyOf applies each subschema to your instance, and asserts valid if at least one of the subschemas passes validation.

{test1: "123" } fails because the root schemas additionalProperties: false prevents any keys in your object not defined in the SAME schema object properties or patternProperties.

The solution is to have some duplication.

In THIS example (link is for in browser testing but draft-7 only), I've added root properties test1 and test2. This will allow data where you have a key of test1 or test2 to pass, but given I don't know your requirements, I can't tell you how to modify the schema to allow an object with a key of test to pass (as each of the anyOf subschemas prevent it).

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "_id": {},
    "test": {},
    "test1": {},
    "test2": {}
  },
  "anyOf": [
    {
      "type": "object",
      "properties": {
        "test1": {}
      },
      "additionalProperties": false
    },
    {
      "type": "object",
      "properties": {
        "test2": {}
      },
      "additionalProperties": false
    }
  ]
}

If your intent is to check that one of the things you are inserting has test1 or test2, then I'm afraid JSON Schema cannot help you. JSON Schema in the context of Mongo can only check each item individually, and is not afforded the ability to validate a collection of potentially inserted records.

In the above example schema, I've removed type checking because that is not relevant to this question, and bsonType differs from JSON Schema type anyway.