I have been trying to validate my data using the validators provided by MongoDB but I have run into a problem. Here is a simple user document which I am inserting.
{
"name" : "foo",
"surname" : "bar",
"books" : [
{
"name" : "ABC",
"no" : 19
},
{
"name" : "DEF",
"no" : 64
},
{
"name" : "GHI",
"no" : 245
}
]
}
Now, this is the validator which has been applied for the user collection. But this is now working for the books array which I am inserting along with the document. I want to check the elements inside the object which are the members of books array. The schema of the object won't change.
db.runCommand({
collMod: "users",
validator: {
$or : [
{ "name" : { $type : "string" }},
{ "surname" : { $type : "string" }},
{ "books.name" : { $type : "string" }},
{ "books.no" : { $type : "number" }}
],
validationLevel: "strict"
});
I know that this validator is for member objects and not for array, but then how do I validate such an object ?
It has been very long since this question was asked.
Anyways, if at all anyone comes through this.
For MongoDB 3.6 and greater version, this can be achieved using the validator.
db.createCollection("users", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name","surname","books"],
properties: {
name: {
bsonType: "string",
description: "must be a string and is required"
},
surname: {
bsonType: "string",
description: "must be a string and is required"
},
books: {
bsonType: [ "array" ],
items: {
bsonType: "object",
required:["name","no"],
properties:{
name:{
bsonType: "string",
description: "must be a string and is required"
},
no:{
bsonType: "number",
description: "must be a number and is required"
}
}
},
description: "must be a array of objects containing name and no"
}
}
}
}
})
This one handles all your requirements.
For more information, refer this link
You can do it in 3.6 using $jsonSchema expression.
JsonSchema allows defining a field as an array and specifying schema constraints for all elements as well as specific constraints for individual array elements.
This blog post has a number of examples which will help you figure out the syntax.
Related
This question already has an answer here:
Mongo Json Schema Validator AnyOf not working
(1 answer)
Closed 3 years ago.
I'm trying to use JSON schema validators in my test collection. It has anyOf validation rule which should accept foo OR bar, should my understanding be valid and correct.
Validation:
{
$jsonSchema: {
bsonType: 'object',
additionalProperties: false,
anyOf: [
{
bsonType: 'object',
properties: {
foo: {
bsonType: 'string'
}
},
additionalProperties: false
},
{
bsonType: 'object',
properties: {
bar: {
bsonType: 'string'
}
},
additionalProperties: false
}
],
properties: {
_id: {
bsonType: 'objectId'
}
}
}
}
Command to insert a document:
rs0:PRIMARY> db.myColl.insert([{foo:"123"}])
Error given:
BulkWriteResult({
"writeErrors" : [
{
"index" : 0,
"code" : 121,
"errmsg" : "Document failed validation",
"op" : {
"_id" : ObjectId("6ee51b4766ba25a01fbcf8u9"),
"foo" : "test123"
}
}
],
"writeConcernErrors" : [ ],
"nInserted" : 0,
"nUpserted" : 0,
"nMatched" : 0,
"nModified" : 0,
"nRemoved" : 0,
"upserted" : [ ]
})
As far as I know, MongoDB supports draft 4 of JSON Schema, as specified here.
Why is it still giving me an error code of 121 (Document failed validation)?
Am I missing something?
Thanks in advance.
As mentioned by #Relequestual, the post in here, helped me to solve my question. Thank you!
Create Collection with validator
db.createCollection("claims",
{ validator : { $jsonSchema : { bsonType : "object",
properties : { airportCode : { bsonType: "string"} },
additionalProperties: false}
} } )
Insert
db.claims.insert({"airportCode" : "DSM"}) => result: "errmsg" : "Document failed validation"
if I remove "additionalProperties: false" by creating the collection, when I can insert the document.
Any advice, how to keep "additionalProperties: false", because I want to control the document.
As at MongoDB 3.6.2, JSON Schema validation does not automatically add the default _id property, so you need to include a rule for this when using additionalProperties: false.
For example, assuming the default ObjectID:
db.createCollection("claims",
{ validator : {
$jsonSchema : {
bsonType : "object",
properties : {
_id: { bsonType: "objectId" },
airportCode : { bsonType: "string"}
},
additionalProperties: false
}
}}
)
Two related issues to upvote/watch on the MongoDB Jira issue tracker:
SERVER-32160: provide warning when _id is not in list of properties and additionalProperties is false
SERVER-20547: Expose the reason an operation fails document validation
I have this document in mongo:
{
id: objectId,
list: [
{
id: internalObjectId1,
enabled: true
},
{
id: internalObjectId2,
enabled: false
}]
}
I need to change the enabled field. How can I do it?
Use the positional $ operator. Suppose you have the following document in the collection whose list element value is an array of embedded documents:
{
"_id" : ObjectId("551be1a04db8a16ac729432e"),
"list" : [
{
"id" : ObjectId("54f43159c922ac0b4387ef9c"),
"enabled" : true
},
{
"id" : ObjectId("54f43159c922ac0b4387ef9d"),
"enabled" : false
}
]
}
The following will update the value of the enabled field in the embedded document with the id of 54f43159c922ac0b4387ef9d to true:
db.collection.update(
{
"_id": ObjectId("551be1a04db8a16ac729432e"),
"list.id": ObjectId("54f43159c922ac0b4387ef9d")
},
{
"$set": {"list.$.enabled": true}
}
)
I looked through the MongoDB documentation and googled this question but couldn't really find a suitable answer.
encounter a problem where I need to search documents in a collection, but 3 fields name will change from one doc to another even though they are always at the same positions.
In the following example, the 366_DAYS can be 2_HOURS, 35_DAYs etc from document to document, but they will be in the same position.
The _XC4ucB8sEeSybaax341rBg will change to another random string from doc to doc, again it will be at the same position for all docs.
Other fields do not change name and stay at the same position.
I want a query to search for records where debitAmount >=creditAmount or endDate > now().
set02:PRIMARY> db.account.find({ _id: "53e51b1b0cf22cb159fa5f38" }).pretty()
{
"_id" : "53e51b1b0cf22cb159fa5f38",
"_version" : 6,
"_transId" : "e3e96377-a2d2-4b75-a946-f621df182c5e-2719",
"accountBalances" : {
"TEST_TIME" : {
"thresholds" : {
},
"deprovisioned" : false,
"quotas" : {
"366_DAYS" : {
"thresholds" : {
},
"quotaCode" : "366_DAYS",
"credits" : {
"_XC4ucB8sEeSybaax341rBg" : {
"startDate" : ISODate("2014-08-08T18:46:51.351Z"),
"creditAmount" : "86460",
"endDate" : ISODate("2014-08-09T18:48:19Z"),
"started" : true,
"debits" : {
"consolidated" : {
"creationDate" : ISODate("2014-08-08T19:15:55.396Z"),
"debitAmount" : "1300",
"debitId" : "consolidated"
}
},
"creditId" : "_XC4ucB8sEeSybaax341rBg"
}
}
}
},
"expiredReservations" : {
},
"accountBalanceCode" : "TEST_TIME",
"reservations" : {
}
}
},
"subscriberId" : "53e51b1b0cf22cb159fa5f38"
}
Can you use arrays for quotas and credits? That would make the path be the same.
"quotas": [
{
"days": 365,
"thresholds": {},
"credits": [
{
"id": "_XC4ucB8sEeSybaax341rBg"
}
]
}
]
Two cases come to mind. Which one applies to you is unclear to me from the question so providing for both possibilities.
CASE 1:
You will always have either 366_DAYS, 2_HOURS or 35_DAYS inside quotas and only one possible creditId per document. If this is the case, then why replicate the quotaCode and the creditId both as a sub-field and as the key inside quotas and credits respectively. You could alter the structure of your document as follows:
{
"_id": "53e51b1b0cf22cb159fa5f38",
"_version": 6,
"_transId": "e3e96377-a2d2-4b75-a946-f621df182c5e-2719",
"accountBalances": {
"TEST_TIME": {
"thresholds": {},
"deprovisioned": false,
"quotas": {
"thresholds": {
},
"quotaCode": "366_DAYS",
"credits": {
"startDate": ISODate("2014-08-08T18:46:51.351Z"),
"creditAmount": "86460",
"endDate": ISODate("2014-08-09T18:48:19Z"),
"started": true,
"debits": {
"consolidated": {
"creationDate": ISODate("2014-08-08T19:15:55.396Z"),
"debitAmount": "1300",
"debitId": "consolidated"
}
},
"creditId": "_XC4ucB8sEeSybaax341rBg"
}
},
"expiredReservations": {
},
"accountBalanceCode": "TEST_TIME",
"reservations": {
}
}
},
"subscriberId": "53e51b1b0cf22cb159fa5f38"
}
Now the fieldPath for fields in your queries would be:
"accountBalances.TEST_TIME.quotas.credits.creditAmount"
"accountBalances.TEST_TIME.quotas.credits.debits.consolidated.debitAmount"
"accountBalances.TEST_TIME.quotas.credits.startDate"
CASE 2:
quotas and credits may contain more than one subdocument. In this case viktortnk's approach of having quotas and credits as arrays will work. The fieldPath for your queries may then be written as:
"accountBalances.TEST_TIME.quotas.[zero-base-index].credits.[zero-base-index].creditAmount"
"accountBalances.TEST_TIME.quotas.[zero-base-index].credits.[zero-base-index].debits.consolidated.debitAmount"
"accountBalances.TEST_TIME.quotas.[zero-base-index].credits.[zero-base-index].startDate"
I have a current relational model for a dynamic question and answer. I am trying to see if it is possible to convert the schema into MongoDB for performance and flexibility.
We basically have a series of questions and question types. These questions are put together in a question set.
The questions are asked in a particular order but for some depending on the answer the next question asked can vary.
For example if Q1=YES then ask question Q9 else ask question Q2
Any ideas on how to design such a schema without having the various relational tavles i currently utilize?
What about something along the lines of this structure:
{
"Questions" :
[
{
"QuestionNumber": "Q1",
"QuestionType" : "YESNO",
"QuestionText" : "Are you happy today?",
"Answers" :
[
{
"Text" : "YES",
"NextQuestionIfAnswered" : "Q9"
},
{
"Text" : "No",
"NextQuestionIfAnswered" : "Q2"
}
],
},
{
"QuestionNumber": "Q2",
"QuestionType" : "MULTIPLE",
"QuestionText" : "Why aren't you happy?",
"Answers" :
[
{
"Text" : "Dog died",
"NextQuestionIfAnswered" : ""
},
{
"Text" : "I'm just generally sad",
"NextQuestionIfAnswered" : ""
}
],
},
{
"QuestionNumber": "Q9",
"QuestionType" : "TEXTBOX",
"QuestionText" : "Type why you are happy into the box below",
"Answers" : []
}
]
}
So you have an array of questions, each with a question number, question type (used for rendering decisions), and each of the possible answers includes the question number that you navigate to when the specified answer is selected.
You could store the user's answers to each question in this document as well by adding an userAnswer property on each of the "Answers" in the array. But depending on your number of users, you may want to keep this in a separate collection.
I designed like this
const { Schema } = mongoose;
const QuestionsSchema = new Schema({
questionId: { type: String },
questionText: { type: String, required: true, unique: true },
options: { type: Array, required: true },
marks: { type: Number, required: true },
difficultyLevel: { type: Number },
questionType: { type: String, required: true },
correctOptions: { type: Array, required: true },
addedAt: { type: Date, default: Date.now }
});
module.exports = mongoose.model("questions", QuestionsSchema, "questions");
API response
"questionText": "Select correct option1?",
"options": [
{
"option1": "1",
"isCorrect": false
},
{
"option2": "2",
"isCorrect": true
},
{
"option3": "3",
"isCorrect": false
},
{
"option3": "3",
"isCorrect": false
}
],
"marks": 1,
"difficultyLevel": 1,
"correctOptions": [
1
],
"questionType": "MCQ"
}