{"id":103508,"date":"2024-08-21T08:26:00","date_gmt":"2024-08-21T08:26:00","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=103508"},"modified":"2024-11-14T22:03:23","modified_gmt":"2024-11-14T22:03:23","slug":"introducing-schema-validation-in-mongodb","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/featured\/introducing-schema-validation-in-mongodb\/","title":{"rendered":"Introducing Schema Validation in MongoDB"},"content":{"rendered":"<p><strong>This article is part of Robert Sheldon's continuing series on Mongo DB. To see all of the items in the series, <a href=\"https:\/\/www.red-gate.com\/simple-talk\/collections\/robert-sheldon-ongoing-mongodb-primer\/\">click here<\/a>.<\/strong><\/p>\n\n<p>Similar to other NoSQL database systems, MongoDB is known for its flexible and variable schema models, unlike relational database systems in which well-defined schemas are essential to ensuring data integrity. In MongoDB, you can add documents to a collection that contain different fields or that include the same fields but with different data types or value ranges. You can even add documents that are completely unrelated. As long as you use proper Binary JSON (BSON) formatting, just about anything goes.<\/p>\n<p>In some cases, however, this flexibility can get to be a little too much, and you might want to impose restrictions on a collection\u2019s documents. In this way, you can better control how data is stored and presented so your applications experience the data in a consistent and reliable manner. For example, you might want to ensure that all documents added to a collection include the <code>name<\/code> field and that the field always takes a <code>string<\/code> value.<\/p>\n<p>You can impose such restrictions on a collection\u2019s documents by defining schema validation rules that specify the acceptable fields and their values. MongoDB\u2019s validation capabilities are flexible and simple to implement and can be easily modified when needed. You can also create rules at a very granular level, even if it\u2019s only a single field value. MongoDB applies the rules to new documents as they\u2019re inserted into the collection and to existing documents when they\u2019re updated. If a document violates those rules, MongoDB rejects the operation.<\/p>\n<p>In this article, I demonstrate how to define validation rules on a collection. The article provides multiple examples of schema definitions that contain different types of validation rules. This is the first of two articles on schema validation. By the end of this article, you should have a good sense of how validation rules work and how to add them to your collections.<\/p>\n<p>Note: For the examples in this article, I used the same MongoDB Atlas and MongoDB Compass environments I used for the previous articles in this series. Refer to the first article for more specifics about setting up these environments.<\/p>\n<h2>Introducing the JSON Schema object<\/h2>\n<p>You can use a MongoDB Shell command to define schema validation on a collection. You can also use the GUI features in MongoDB Compass, but you still need to understand how to build the validation rules themselves, and MongoDB Shell is a good place to start.<\/p>\n<p>When adding schema validation to a collection, you must use the <code>validator<\/code> method to create a JSON Schema object, which defines the validation rules. As part of this process, you must also use the <code>$jsonSchema<\/code> operator to build the actual rules.<\/p>\n<p>You can define the JSON Schema object when you first add your collection to the database or after the collection already exists. In either case, the format you use for invoking the <code>validator<\/code> method is the same. The following syntax shows this format, which starts with calling the <code>validator<\/code> method:<\/p>\n<pre class=\"lang:none theme:none\">validator: { $jsonSchema: { &lt;JSON Schema object&gt; } }<\/pre>\n<p>As the syntax shows, you pass the <code>$jsonSchema<\/code> operator in as an argument to the <code>validator<\/code> method. The <code>$jsonSchema<\/code> operator, which is enclosed in curly brackets, defines the JSON Schema object, which is also enclosed in curly brackets.<\/p>\n<p>The schema object itself is based on <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/draft-zyp-json-schema-04\">draft 4 of the JSON Schema standard<\/a>. MongoDB omits several elements from the standard, while also extending it to support MongoDB\u2019s BSON data types. A full explanation of the JSON Schema standard and its implementation in MongoDB is beyond the scope of this article, but you can find more information about how MongoDB implements the JSON Schema in the MongoDB topic <a href=\"https:\/\/www.mongodb.com\/docs\/manual\/reference\/operator\/query\/jsonSchema\/\">$jsonSchema<\/a>.<\/p>\n<p>A good way to learn how to define validation rules is to see them in action. To this end, I\u2019ve created a series of examples that demonstrate the core components that go into a collection\u2019s schema validation object. The examples use the <code>hr<\/code> database and <code>candidates<\/code> collection, but you can use any database or collection, preferably one that\u2019s empty and not deployed to a production environment.<\/p>\n<p>If you want to try out these examples yourself, I recommend that you stick with the <code>hr<\/code> database and <code>candidates<\/code> collection to keep things simple. At this point in the series, you should have no trouble creating a database and collection. Refer to previous articles in this series if necessary.<\/p>\n<p>When I created the examples, I used the version of MongoDB Shell that\u2019s embedded in the MongoDB Compass GUI. I like this version of Shell because I can easily verify changes to my documents by viewing them in the Compass GUI. That said, if you have MongoDB Shell installed on your system, you can instead use your system\u2019s command-line interface (CLI) to try out these examples. The results are the same in either case.<\/p>\n<p>For this article, I created the examples based on an existing collection (<code>candidates<\/code>), rather than defining them when creating the collection. This approach makes it easier to run through the examples without needing to drop the collection before re-creating it. It also makes it easier to reuse the statement as you refine the rules.<\/p>\n<h2>Adding validation rules to a MongoDB collection<\/h2>\n<p>To add validation rules to the <code>candidates<\/code> collection, we\u2019ll start by using the <code>runCommand<\/code> database method to call the <code>collMod<\/code> database command. The command let\u2019s us add options to a collection, which in this case, are the validation rules. We\u2019ll use the command to call the <code>validator<\/code> method and, subsequently, the <code>$jsonSchema<\/code> operator, which defines the JSON Schema object. The following example demonstrates how all this works:<\/p>\n<pre class=\"lang:none theme:none\">db.runCommand( { collMod: \"candidates\",\n  validator: {\n    $jsonSchema: {\n      bsonType: \"object\",\n      title: \"candidates validation\",\n      required: [ \"name\", \"dob\", \"position\" ],\n      properties: {\n        \"name\": {\n          bsonType: \"string\",\n          description: \"Field required and must be a string.\"\n        },\n        \"dob\": {\n          bsonType: \"date\",\n          description: \"Field required and must be a date.\"\n        },\n        \"position\": {\n          bsonType: \"object\",\n          required: [ \"title\", \"dept\" ],\n          properties: {\n            \"title\": {\n              bsonType: \"string\",\n              description: \"Field required and must be a string.\" \n            },\n            \"dept\": { \n              bsonType: \"string\",\n              enum: [ \"R&amp;D\", \"Marketing\", \"IT\", \"HR\", \"Finance\" ],\n              description: \"Field required and must be one of the specified values.\" \n            },\n            \"skills\": { \n              bsonType: \"array\",\n              description: \"Field must be an array, if included.\"\n            },\n            \"yrs_exp\": { \n              bsonType: \"int\",\n              minimum: 3,\n              description: \"Field must be an int greater than 4, if included.\"\n            }\n          }\n        }\n      }\n    }\n  }\n});<\/pre>\n<p>The entire statement is passed into MongoDB Shell as a single command. As noted earlier, the command adds the validation rules to an existing collection (<code>candidates<\/code>). If you want to add the rules when creating the collection, you must include the <code>validator<\/code> object as an argument to the <code>createCollection<\/code> method. The MongoDB topic <a href=\"https:\/\/www.mongodb.com\/docs\/manual\/core\/schema-validation\/specify-json-schema\/\">Specify JSON Schema Validation<\/a> shows an example of how this is done.<\/p>\n<p>Returning to the example above, the first three lines of the command are fairly standard when defining validation rules:<\/p>\n<ol>\n<li>Invoke the <code>runCommand<\/code> method on the database object associated with the <code>hr<\/code> database. The method runs the <code>collMod<\/code> database command. The command\u2019s first argument is the <code>candidates<\/code> collection.<\/li>\n<li>Specify the <code>validator<\/code> method as the second argument to the <code>collMod<\/code> command.<\/li>\n<li>Specify the <code>$jsonSchema<\/code> operator as an argument to the <code>validator<\/code> method.<\/li>\n<\/ol>\n<p>The remaining code, enclosed in curly brackets, defines the JSON Schema object that is returned by the <code>$jsonSchema<\/code> operator. A JSON Schema object is essentially a JSON document. Each line is a schema element that contains a keyword, followed by a value, much like a JSON document in which each field is followed by the field value. The elements are organized hierarchically. In this case, the following four elements at the top of the hierarchy:<\/p>\n<ul>\n<li><code><strong>bsonType.<\/strong><\/code> Indicates the data type of that particular element. When <code>bsonType<\/code> is included as a top-level element, as it is here, the data type is <code>object<\/code> and refers to the JSON Schema object as a whole. This element is often omitted from the hierarchy\u2019s top level.<\/li>\n<li><code><strong>title<\/strong><\/code><strong>.<\/strong> Provides a name for the set of validation rules. This element is often omitted from the schema definition.<\/li>\n<li><code><strong>required<\/strong>.<\/code> Indicates which fields are required in the collection\u2019s documents. The element\u2019s value is an array of string values that list the field names. Any fields within the array must be included in the document. The element is omitted from the schema definition if no fields are required.<\/li>\n<li><code><strong>properties<\/strong>.<\/code> Defines specific properties associated with the listed fields, which are included as subelements within the <code>properties<\/code> element, much like an embedded document. Each document field must adhere to the schema defined for that subelement. The <code>properties<\/code> element is omitted from the schema definition if no field properties need to be defined.<\/li>\n<\/ul>\n<p>In this case, the top-level <code>properties<\/code> element includes the three field subelements: <code>name<\/code>, <code>dob<\/code>, and <code>position<\/code>. The subelements for the <code>name<\/code> and <code>dob<\/code> fields specify the data type and provide a description for each field. When a data type is specified, the field\u2019s value must conform to that type. The description is used when returning an error message relevant to that subelement.<\/p>\n<p>The third subelement applies to the <code>position<\/code> field, which is an embedded document. The <code>position<\/code> subelement includes its own subelements: <code>bsonType<\/code>, <code>required<\/code>, and <code>properties<\/code>.<\/p>\n<p>In this case, too, the <code>properties<\/code> subelement is broken down further into its own field subelements: <code>title<\/code>, <code>dept<\/code>, <code>skills<\/code>, and <code>yrs_exp<\/code>. All four of these embedded subelements define the data type and provide a description. There are also a couple new element types.<\/p>\n<ul>\n<li>The <code>dept<\/code> subelement includes the <code>enum<\/code> element, which defines the values that the <code>position.dept<\/code> field can include. The values are defined as an array of stings. No other values can be inserted into this field.<\/li>\n<li>The <code>yrs_exp<\/code> subelement includes the <code>minimum<\/code> element, with its value set to <code>3<\/code>. As a result, the value inserted into the <code>position.yrs_exp<\/code> field must be <code>3<\/code> or greater.<\/li>\n<\/ul>\n<p>Those are all the elements that make up this particular JSON Schema object. You can include fewer details in your schema definition, or you can include more. You can also include element types not shown here. The MongoDB topic <a href=\"https:\/\/www.mongodb.com\/docs\/manual\/reference\/operator\/query\/jsonSchema\/\">$jsonSchema<\/a> includes a list of available element types\u2014or keywords\u2014that MongoDB supports for schema validation.<\/p>\n<h2>Verifying a collection\u2019s schema validation rules<\/h2>\n<p>After you define validation rules on the <code>candidates<\/code> collection, you\u2019ll likely want to verify that the rules are working as expected. A good way to do this is to try to insert a document into the collection. For example, the following <code>insertOne<\/code> command tries to add a document that includes all the fields referencd in the validation rules:<\/p>\n<pre class=\"lang:none theme:none\">db.candidates.insertOne({ \n  \"name\": \"Drew\", \n  \"dob\": \"1973-9-12\",\n  \"position\": { \n    \"title\": \"Senior Developer\", \n    \"dept\": \"R&amp;D\",\n    \"skills\": [ \"Java\", \"R\", \"Python\", \"PHP\" ],\n    \"yrs_exp\": 18 }\n});<\/pre>\n<p>On the surface, it might appear that you should be able to add this document with no problem. However, the validation rules specify that the <code>dob<\/code> field can take only a <code>date<\/code> value, while the command tries to add it as a <code>string<\/code> value. As a result, MongoDB returns the following error, which indicates that there is a type mismatch:<\/p>\n<pre class=\"lang:none theme:none\">MongoServerError: Document failed validation\nAdditional information:\n{\n  failingDocumentId: {\n    buffer: &lt;Buffer 66 84 1d fd 9f 07 2e ed ef c2 87 34&gt;\n  },\n  details: {\n    operatorName: '$jsonSchema',\n    title: 'candidates validation',\n    schemaRulesNotSatisfied: [\n      {\n        operatorName: 'properties',\n        propertiesNotSatisfied: [\n          {\n            propertyName: 'dob',\n            description: 'Field required and must be a date.',\n            details: [\n              {\n                operatorName: 'bsonType',\n                specifiedAs: {\n                  bsonType: 'date'\n                },\n                reason: 'type did not match',\n                consideredValue: '1973-9-12',\n                consideredType: 'string'\n              }\n            ]\n          }\n        ]\n      }\n    ]\n  }\n}<\/pre>\n<p>Notice that the error message includes the <code>description<\/code> element that was defined on the <code>dob<\/code> field. The message also indicates that the type did not match what was expected. To correct this issue, you need to pass in the <code>dob<\/code> value as a <code>date<\/code> type, as shown in the next example:<\/p>\n<pre class=\"lang:none theme:none\">db.candidates.insertOne({ \n  \"name\": \"Drew\", \n  \"dob\": new Date(\"1973-9-12\"),\n  \"position\": { \n    \"title\": \"Senior Developer\", \n    \"dept\": \"R&amp;D\",\n    \"skills\": [ \"Java\", \"SQL\", \"Python\", \"PHP\" ],\n    \"yrs_exp\": 18 }\n});<\/pre>\n<p>You should now be able to insert the document with no problem. MongoDB will then return a confirmation message similar to the following, although with a different <code>insertedId<\/code> value:<\/p>\n<pre class=\"lang:none theme:none\">{\n  acknowledged: true,\n  insertedId: ObjectId('6683317721ea5e563566c9ec')\n}<\/pre>\n<p>You can also use an <code>updateOne<\/code> command to confirm that your validation rules are working as expected. For example, the following <code>updateOne<\/code> command tries to update the <code>Drew<\/code> document by setting the <code>position.dept<\/code> value to <code>Dev<\/code>:<\/p>\n<pre class=\"lang:none theme:none\">db.candidates.updateOne(\n  { \"name\" : \"Drew\" },\n  { $set: { \"position.dept\": \"Dev\" } }\n);<\/pre>\n<p>As you\u2019ll recall from the collection\u2019s validation rules, the <code>position.dept<\/code> value must be one of those specified in the <code>enum<\/code> array. <code>Dev<\/code> is not in that array, so the document cannot be updated in this way. If you try to run this command, you\u2019ll receive an error message indicating that <code>Dev<\/code> was not found in <code>enum<\/code>.<\/p>\n<p>Another way you can test your validation rules is to try to update a document by changing the <code>position.yrs_exp<\/code> value to <code>2<\/code>, as in the following example:<\/p>\n<pre class=\"lang:none theme:none\">db.candidates.updateOne(\n  { \"name\" : \"Drew\" },\n  { $set: { \"position.yrs_exp\": 2 } }\n);<\/pre>\n<p>In this case, the validation rules state that the <code>position.yrs_exp<\/code> value must be at least <code>3<\/code>, so MongoDB will again prevent you from updating the document. Instead, you\u2019ll receive an error message indicating that your comparison failed.<\/p>\n<h2>Controlling permitted fields in a collection\u2019s document<\/h2>\n<p>By default, schema validation is concerned only with the fields that are specified within the validation rules. In other words, there is nothing to prevent you from adding other fields to your documents. For example, you might add a document to the <code>candidates<\/code> collection that includes a field describing the candidate\u2019s personal interests or hobbies.<\/p>\n<p>In some cases, however, you might want to ensure that the only fields included in a document are those defined within the <code>properties<\/code> element. You can do this by adding the <code>additionalProperties<\/code> element to your schema definition and setting its value to <code>false<\/code>, as in the following example:<\/p>\n<pre class=\"lang:none theme:none\">db.runCommand( { collMod: \"candidates\",\n  validator: {\n    $jsonSchema: {\n      bsonType: \"object\",\n      title: \"candidates validation\",\n      required: [ \"name\", \"dob\", \"position\" ],\n      additionalProperties: false,\n      properties: {\n        \"name\": {\n          bsonType: \"string\",\n          description: \"Field required and must be a string.\"\n        },\n        \"dob\": {\n          bsonType: \"date\",\n          description: \"Field required and must be a date.\"\n        },\n        \"position\": {\n          bsonType: \"object\",\n          required: [ \"title\", \"dept\" ],\n          properties: {\n            \"title\": {\n              bsonType: \"string\",\n              description: \"Field required and must be a string.\" \n            },\n            \"dept\": { \n              bsonType: \"string\",\n              enum: [ \"R&amp;D\", \"Marketing\", \"IT\", \"HR\", \"Finance\" ],\n              description: \"Field required and must be a string.\" \n            },\n            \"skills\": { \n              bsonType: \"array\",\n              description: \"Field must be an array if included.\"\n            },\n            \"yrs_exp\": { \n              bsonType: \"int\",\n              minimum: 3,\n              description: \"Field must be an int if included.\"\n            }\n          }\n        }\n      }\n    }\n  }\n});<\/pre>\n<p>This command is the same as the previous validation example, except that it now includes the <code>additionalProperties<\/code> element. If you run this command in MongoDB Shell, it will automatically update your validation rules on the <code>candidates<\/code> collection. You don\u2019t need to take any other steps to update the rules.<\/p>\n<p>Although adding the <code>additionalProperties<\/code> element is fairly straightforward, you must be careful when doing so. For example, you might expect to be able to run the following <code>insertOne<\/code> command, which tries to add another document to the <code>candidates<\/code> collection:<\/p>\n<pre class=\"lang:none theme:none\">db.candidates.insertOne({ \n  \"name\": \"Parker\", \n  \"dob\": new Date(\"1982-12-2\"),\n  \"position\": { \n    \"title\": \"Data Scientist\", \n    \"dept\": \"R&amp;D\",\n    \"yrs_exp\": 14 }\n});<\/pre>\n<p>The document does not include any fields that are not defined in the <code>properties<\/code> element, nor does it appear to violate any rules defined on the individual fields. However, if you try to run this command, you\u2019ll receive an error stating that your document contains a property (field) not defined in the <code>properties<\/code> element. The error, as it turns out, is related to the <code>_id<\/code> field.<\/p>\n<p>Each document in a MongoDB collection must include the <code>_id<\/code> field. If you don\u2019t include the field in your document definition, MongoDB will automatically add it. However, the validation rules, as they\u2019re currently defined, do not specify this field, so any document you try to add the collection will fail validation.<\/p>\n<p>To address this issue, you must specify the <code>_id<\/code> field within the <code>properties<\/code> element, along with the other fields, as shown in the following example:<\/p>\n<pre class=\"lang:none theme:none\">db.runCommand( { collMod: \"candidates\",\n  validator: {\n    $jsonSchema: {\n      bsonType: \"object\",\n      title: \"candidates validation\",\n      required: [ \"_id\", \"name\", \"dob\", \"position\" ],\n      additionalProperties: false,\n      properties: {\n        \"_id\": {\n          bsonType: \"objectId\",\n          description: \"Field required and must be an objectID.\"\n        },\n        \"name\": {\n          bsonType: \"string\",\n          description: \"Field required and must be a string.\"\n        },\n        \"dob\": {\n          bsonType: \"date\",\n          description: \"Field required and must be a date.\"\n        },\n        \"position\": {\n          bsonType: \"object\",\n          required: [ \"title\", \"dept\" ],\n          properties: {\n            \"title\": {\n              bsonType: \"string\",\n              description: \"Field required and must be a string.\" \n            },\n            \"dept\": { \n              bsonType: \"string\",\n              enum: [ \"R&amp;D\", \"Marketing\", \"IT\", \"HR\", \"Finance\" ],\n              description: \"Field required and must be a string.\" \n            },\n            \"skills\": { \n              bsonType: \"array\",\n              description: \"Field must be an array if included.\"\n            },\n            \"yrs_exp\": { \n              bsonType: \"int\",\n              minimum: 3,\n              description: \"Field must be an int if included.\"\n            }\n          }\n        }\n      }\n    }\n  }\n});<\/pre>\n<p>As you can see, the top-level <code>properties<\/code> element now includes a subelement for the <code>_id<\/code> field, with its data type defined as <code>objectId<\/code>. I also included <code>_id<\/code> field in the <code>required<\/code> element just for completeness.<\/p>\n<p>After you update the schema, you can verify your changes by rerunning the previous <code>insertOne<\/code> command:<\/p>\n<pre class=\"lang:none theme:none\">db.candidates.insertOne({ \n  \"name\": \"Parker\", \n  \"dob\": new Date(\"1982-12-2\"),\n  \"position\": { \n    \"title\": \"Data Scientist\", \n    \"dept\": \"R&amp;D\",\n    \"yrs_exp\": 14 }\n});<\/pre>\n<p>You should now be able to insert the document with no problem. However, this only shows that that you can add a document that is in the expected format. Another test you can perform is to try to add a document that contains a field not defined in the <code>properties<\/code> element, as in the following example:<\/p>\n<pre class=\"lang:none theme:none\">db.candidates.insertOne({ \n  \"name\": \"Harper\", \n  \"dob\": new Date(\"1967-3-25\"),\n  \"region\": \"southeast\",\n  \"position\": { \n    \"title\": \"Marketing Manager\", \n    \"dept\": \"Marketing\",\n    \"yrs_exp\": 22 }\n});<\/pre>\n<p>The command attempts to add a document that includes the <code>region<\/code> field. If you try to run this command, you will receive an error message stating that <code>region<\/code> is an additional property. To address this issue, you can simply remove the offending field, as in the following <code>insertOne<\/code> statement:<\/p>\n<pre class=\"lang:none theme:none\">db.candidates.insertOne({ \n  \"name\": \"Harper\", \n  \"dob\": new Date(\"1967-3-25\"),\n  \"position\": { \n    \"title\": \"Marketing Manager\", \n    \"dept\": \"Marketing\",\n    \"yrs_exp\": 22 }\n});<\/pre>\n<p>When you run this version of the command, you should be able to insert the document with no problem.<\/p>\n<h2>Working with null values when validating schema<\/h2>\n<p>In some cases, you might want to be able to insert a <code>null<\/code> for a field value when adding or updating a document. For example, an application might automatically default to <code>null<\/code> if a value is not known, rather than excluding the field from the document. If <code>null<\/code> values are going to be used, you must take them into account when configuring validation rules.<\/p>\n<p>For example, suppose the <code>position.title<\/code> field might take a <code>null<\/code> value in some cases, as in the following <code>updateOne<\/code> command:<\/p>\n<pre class=\"lang:none theme:none\">db.candidates.updateOne(\n  { \"name\" : \"Harper\" },\n  { $set: { \"position.title\": null } }\n);<\/pre>\n<p>If you try to run this statement, MongoDB will generate an error message stating that the type does not match. This is because the validation rules currently state that the <code>position.title<\/code> field permits <code>string<\/code> values only. However, you can easily fix this issue by updating the <code>position.title<\/code> subelement in the schema definition, as in the following example:<\/p>\n<pre class=\"lang:none theme:none\">db.runCommand( { collMod: \"candidates\",\n  validator: {\n    $jsonSchema: {\n      bsonType: \"object\",\n      title: \"candidates validation\",\n      required: [ \"_id\", \"name\", \"dob\", \"position\" ],\n      additionalProperties: false,\n      properties: {\n        \"_id\": {\n          bsonType: \"objectId\",\n          description: \"Field required and must be an objectID.\"\n        },\n        \"name\": {\n          bsonType: \"string\",\n          description: \"Field required and must be a string.\"\n        },\n        \"dob\": {\n          bsonType: \"date\",\n          description: \"Field required and must be a date.\"\n        },\n        \"position\": {\n          bsonType: \"object\",\n          required: [ \"title\", \"dept\" ],\n          properties: {\n            \"title\": {\n              bsonType: [ \"string\", \"null\" ],\n              description: \"Field required and must be a string.\" \n            },\n            \"dept\": { \n              bsonType: \"string\",\n              enum: [ \"R&amp;D\", \"Marketing\", \"IT\", \"HR\", \"Finance\" ],\n              description: \"Field required and must be a string.\" \n            },\n            \"skills\": { \n              bsonType: \"array\",\n              description: \"Field must be an array if included.\"\n            },\n            \"yrs_exp\": { \n              bsonType: \"int\",\n              minimum: 3,\n              description: \"Field must be an int if included.\"\n            }\n          }\n        }\n      }\n    }\n  }\n});<\/pre>\n<p>Notice that I\u2019ve updated the <code>bsonType<\/code> value for the <code>position.title<\/code> subelement. The value is now an array that includes both <code>string<\/code> and <code>null<\/code> as acceptable types. After you update the schema definition, you can then try to rerun the <code>updateOne<\/code> command. MongoDB should now update the document and return the following message:<\/p>\n<pre class=\"lang:none theme:none\">{\n  acknowledged: true,\n  insertedId: null,\n  matchedCount: 1,\n  modifiedCount: 1,\n  upsertedCount: 0\n}<\/pre>\n<p>That\u2019s all there is to handling <code>null<\/code> values when defining validation rules. You can do this for any field in which <code>null<\/code> might be an acceptable value. That said, you need to do this only for those fields included in the <code>properties<\/code> element.<\/p>\n<h2>Adding query operators to your schema validation rules<\/h2>\n<p>In some cases, you might want to include additional logic to your schema definition to better control field values when inserting or updating documents. For this, you can use query operators to build expressions that apply different types of logic to your documents.<\/p>\n<p>For example, suppose you want to ensure that all job candidates are at least 21 years old. You can update your schema definition to include this logic, along with the original JSON Schema object, as shown in the following example:<\/p>\n<pre class=\"lang:none theme:none\">db.runCommand( { collMod: \"candidates\",\n  validator: {\n    \"$and\": [\n      {\n        \"$expr\": {\n          \"$lte\": [ \"$dob\", \n            { $dateSubtract: \n              { startDate: new Date(), unit: \"year\", amount: 21 } \n            } \n          ]\n        }\n      },\n      {\n        $jsonSchema: {\n          bsonType: \"object\",\n          title: \"candidates validation\",\n          required: [ \"_id\", \"name\", \"dob\", \"position\" ],\n          additionalProperties: false,\n          properties: {\n            \"_id\": {\n              bsonType: \"objectId\",\n              description: \"Field required and must be an objectID.\"\n            },\n            \"name\": {\n              bsonType: \"string\",\n              description: \"Field required and must be a string.\"\n            },\n            \"dob\": {\n              bsonType: \"date\",\n              description: \"Field required and must be a date.\"\n            },\n            \"position\": {\n              bsonType: \"object\",\n              required: [ \"title\", \"dept\" ],\n              properties: {\n                \"title\": {\n                  bsonType: [ \"string\", \"null\" ],\n                  description: \"Field required and must be a string.\" \n                },\n                \"dept\": { \n                  bsonType: \"string\",\n                  enum: [ \"R&amp;D\", \"Marketing\", \"IT\", \"HR\", \"Finance\" ],\n                  description: \"Field required and must be a string.\" \n                },\n                \"skills\": { \n                  bsonType: \"array\",\n                  description: \"Field must be an array if included.\"\n                },\n                \"yrs_exp\": { \n                  bsonType: \"int\",\n                  minimum: 3,\n                  description: \"Field must be an int if included.\"\n                }\n              }\n            }\n          }\n        }\n      }\n    ]\n  }\n});<\/pre>\n<p>The first thing to notice is that the first <code>validator<\/code> argument now begins with the <code>$and<\/code> logical operator, which is followed by two conditions: an expression that calculates the age based on the <code>dob<\/code> field and the JSON Schema object definition, which is the same one from the previous validation example. Both of these conditions must be met to be able to insert or update a document.<\/p>\n<p>The JSON Schema object should need no further explanation because nothing has changed, so let\u2019s take a closer look at the expression, which begins with the <code>$expr<\/code> evaluation operator. The operator lets us define an aggregation expression within our query. The aggregation expression is everything enclosed in the curly brackets that follow the <code>$expr<\/code> keyword.<\/p>\n<p>That expression uses the <code>$lte<\/code> logical operator to indicate that the <code>dob<\/code> value (represented as <code>$dob<\/code>) must be less than or equal to the value returned by the <code>$dateSubtract<\/code> operator. The <code>$dateSubtract<\/code> operator subtracts <code>21<\/code> years from the current date to arrive at a <code>date<\/code> value that is then compared to the <code>dob<\/code> value. If the <code>dob<\/code> value occurs before the returned <code>date<\/code> value, the document can be added.<\/p>\n<p>After you update the schema definition, you can test out your changes by running the following statement.<\/p>\n<pre class=\"lang:none theme:none\">db.candidates.insertOne({ \n  \"name\": \"Darcy\", \n  \"dob\": new Date(\"2004-7-2\"),\n  \"position\": { \n    \"title\": \"Developer\", \n    \"dept\": \"R&amp;D\",\n    \"skills\": [ \"Java\", \"Csharp\", \"Python\", \"R\" ],\n    \"yrs_exp\": 3 }\n});<\/pre>\n<p>The statement uses <code>2004-7-2<\/code> as the <code>dob<\/code> value, which should cause the statement to fail. If by the time you read this article, the date is no longer less than 21 years from the current date, you\u2019ll need to adjust the value. The idea is to get the command to generate an error as a result of trying to add a <code>dob<\/code> value that is below the required age. When the command fails, you should get an error message stating that the expression did not match.<\/p>\n<p>Next, run the following <code>insertOne<\/code> command, which is the same as the previous one, except that the <code>dob<\/code> value is now definitely more then 21 years ago:<\/p>\n<pre class=\"lang:none theme:none\">db.candidates.insertOne({ \n  \"name\": \"Darcy\", \n  \"dob\": new Date(\"2003-7-2\"),\n  \"position\": { \n    \"title\": \"Developer\", \n    \"dept\": \"R&amp;D\",\n    \"skills\": [ \"Java\", \"Csharp\", \"Python\", \"R\" ],\n    \"yrs_exp\": 3 }\n});<\/pre>\n<p>The command should run with no problem because it does not violate any of the validation rules.<\/p>\n<p>There are, of course, plenty of other ways you can define your expressions so you can include different logic in your <code>validator<\/code> object. You can, in fact, define an expression without including a JSON Schema object. For more information about using query operators, see the MongoDB topic <a href=\"https:\/\/www.mongodb.com\/docs\/manual\/core\/schema-validation\/specify-query-expression-rules\/#std-label-schema-validation-query-expression\">Specify Validation With Query Operators<\/a>.<\/p>\n<h2>Getting started with MongoDB validation rules<\/h2>\n<p>For the examples in this article, I used MongoDB\u2019s default validation settings. In the next article, I plan to continue the discussion on validation rules, at which time, I\u2019ll provide more details on how you can override the default behavior. I\u2019ll also discuss validation rules as they apply to documents that already exist within a collection. In the meantime, I suggest you review the MongoDB topic <a href=\"https:\/\/www.mongodb.com\/docs\/manual\/core\/schema-validation\/\">Schema Validation<\/a>, which introduces you to validation rules. I think you\u2019ll find that schema validation is a valuable tool for working with MongoDB data, although you\u2019ll likely want to limit its use to more mature applications when the schema is relatively stable.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Similar to other NoSQL database systems, MongoDB is known for its flexible and variable schema models, unlike relational database systems in which well-defined schemas are essential to ensuring data integrity. In MongoDB, you can add documents to a collection that contain different fields or that include the same fields but with different data types or&#8230;&hellip;<\/p>\n","protected":false},"author":221841,"featured_media":103512,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[53,159161],"tags":[5618,159226],"coauthors":[6779],"class_list":["post-103508","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-featured","category-mongodb","tag-mongodb","tag-mongodbseriesrobertsheldon"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/103508","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221841"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=103508"}],"version-history":[{"count":2,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/103508\/revisions"}],"predecessor-version":[{"id":103511,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/103508\/revisions\/103511"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/103512"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=103508"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=103508"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=103508"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=103508"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}