JSON Schema Validation - Dependencies you can depend on

In Part 1 of this series, we explored how to get started with JSON Schema Validation in MongoDB. We looked at how to define a schema, require specific properties in that schema, and assign types to those fields. In Part 2 we took a look a deeper look at how to validate unique array objects. In this last part of the Schema Validation series, I’d like to take a look at how to make properties in a schema dependent on other properties.

Dependencies

Being able to define a schema based on other properties can be a great benefit in that it can move application logic into the database itself. There are two forms of dependencies we should look at:

  • Property dependencies which state that if a specified property is present, other properties must be present as well, and
  • Schema dependencies which define a change in the schema when a given property is present.

Property Dependencies

An example use case of property dependencies would be students. If a student has graduated from a program, we want to stay in touch with them and want their mailing address to be a required field. If they haven’t graduated, it isn’t necessary.

db.students.drop()
db.createCollection ( "students",
{
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["name"],
      properties: {
        _id: {},
        name: {
          bsonType: ["string"],
          description: "'name' is a required string"
        },
        graduated: {
          bsonType: ["bool"],
          description: "'graduated' is an optional boolean value"
        }
      },
      dependencies: {
        graduated: ["mailing_address"]
      }
    }
  }
})
Defining a property dependency

With the addition of the dependencies keyword, we’ve defined that if graduated exists, so must mailing_address. One thing to note here is that this dependency is not bidirectional, therefore doing this insert would still work:

db.students.insertOne({name: "Andrei Popov", mailing_address: "456 Oak Street"}) 
Inserting just a mailing address

In order to make the dependencies bi-directional, we would need to explicitly define them:

...
dependencies: {
            "graduated": ["mailing_address"],
            "mailing_address": ["graduated"]
        }
...
A bi-directional dependency

With those bi-directional dependencies in place, neither of these inserts would work:

db.students.insertOne({name: "Jamal Aattou", graduated: true}) 
This doesn't work as there's no mailing_address
db.students.insertOne({name: "Courtney DeSaja", mailing_address: "789 Broadway"})
This doesn't work as there's no graduated field

Schema Dependencies

Schema dependencies extend the schema to have additional constraints. If we used the schema dependency technique instead of the single direction property dependency technique we could achieve the same results as follows:

db.students.drop()
db.createCollection ( "students",
{
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["name"],
      properties: {
        _id: {},
        name: {
          bsonType: ["string"],
          description: "'name' is a required string"
        },
        graduated: {
          bsonType: ["bool"],
          description: "'graduated' is an optional boolean value"
        }
      },
      dependencies: {
        graduated: {
          required: ["mailing_address"],
          properties: {
            mailing_address: {
              bsonType: ["string"]
            }
          }
        }
      }
    }
  }
})
Defining a schema dependency

If we try a few sample inserts into the students collection with those rules defined:

db.students.insertOne({name: "Alena Weber", 
        graduated: true, 
        mailing_address: "123 Main Street"})
Example 1
db.students.insertOne({name: "Jamal Aattou", 
        graduated: true})
Example 2
db.students.insertOne({name: "Chris T. Barker",
        mailing_address: "987 Pine St NE"})
Example 3

In these instances, example 1 and 3 both will be successfully inserted into the database, while example 2 will fail because of a lack of the required mailing_address field.

MongoDB Stitch Schema Validation

When you are using MongoDB Stitch there is another validation option available. Stitch allows for a validate expression. These expressions increase the power of schema validation by allowing Stitch functions to be called during the validation process. This can allow for the validation of changes to documents and have access to previous values of a field. For example, if you wanted to update a widget’s owner_id value, you could call a function to make sure the new value is assigned to an owner who doesn’t have any widget’s assigned to them.

Conclusion

MongoDB’s flexible document model is one of the database’s best features for being able to rapidly create applications. Once a schema is created and optimized for data access performance, it is often helpful to be able to “lock it in” for production. This provides many potential benefits, including:

  • The introduction of concrete milestones in the evolution of your data model which you can test against,
  • Ensure there are no unintended changes to the schema,
  • Ensure that only expected data types or structures exist in a specific field,
  • Only accepting approved data

With the techniques used in this JSON Schema Validation series, you now have another tool in your MongoDB toolbox. We’ve also made it easy to add validation rules to your collections inside MongoDB Compass. Check out this article on how to put the validation tool to use in MongoDB Compass, the tool that makes it easy to explore and manipulate your MongoDB data. Use these schema validation techniques to further extend the power of the document model, and make your applications even better.

JSON Schema Validation Series