Kata kunci bsonType, required, minimum, dan pattern sudah cukup untuk sebagian besar field. Chapter ini menggabungkan semuanya menjadi kontrak utuh, lalu menambahkan tiga alat baru: validasi isi array lewat items, penutupan field asing lewat additionalProperties, dan aturan bersyarat lewat oneOf. Hasil akhirnya adalah validator yang menjaga bukan hanya tipe, tetapi juga bentuk menyeluruh sebuah document.
Kontrak Utuh untuk Satu Collection #
Kita rangkum semua yang sudah dipelajari ke dalam satu validator produk, plus aturan array untuk field tag:
use latihan_validasi
db.produk.drop()
db.createCollection("produk", { validator: { $jsonSchema: {
bsonType: "object",
required: ["nama", "sku", "harga", "kategori", "stok"],
properties: {
nama: { bsonType: "string", minLength: 3, maxLength: 60 },
sku: { bsonType: "string", pattern: "^[A-Z]{3}-[0-9]{3}$" },
harga: { bsonType: "int", minimum: 500, multipleOf: 500 },
kategori: { enum: ["Minuman", "Makanan", "Alat Tulis"] },
stok: { bsonType: "int", minimum: 0, maximum: 100000 },
tag: {
bsonType: "array",
items: { bsonType: "string" },
minItems: 1,
maxItems: 5,
uniqueItems: true
}
}
} } })
db.produk.insertOne({ nama: "Kopi Gayo 250g", sku: "MIN-001", harga: 85000, kategori: "Minuman", stok: 40, tag: ["kopi", "arabika"] })Output yang Dihasilkan:
switched to db latihan_validasi
true
{ ok: 1 }
{
acknowledged: true,
insertedId: ObjectId('6ad390dcd79732f7a1fc9d2e')
}Kata kunci array bekerja seperti namanya: items menentukan skema untuk setiap elemen, minItems/maxItems membatasi jumlah elemen, dan uniqueItems: true melarang duplikat. Dua pelanggaran berikut menguji multipleOf pada harga dan uniqueItems pada tag:
db.produk.insertOne({ nama: "Gula Pasir 1kg", sku: "MAK-001", harga: 15100, kategori: "Makanan", stok: 10 })
db.produk.insertOne({ nama: "Gula Pasir 1kg", sku: "MAK-001", harga: 15000, kategori: "Makanan", stok: 10, tag: ["sembako", "sembako"] })Output yang Dihasilkan:
Uncaught:
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: ObjectId('6ad390dcd79732f7a1fc9d2f'),
details: {
operatorName: '$jsonSchema',
schemaRulesNotSatisfied: [
{
operatorName: 'properties',
propertiesNotSatisfied: [
{
propertyName: 'harga',
details: [
{
operatorName: 'multipleOf',
specifiedAs: { multipleOf: 500 },
reason: 'considered value is not a multiple of the specified value',
consideredValue: 15100
}
]
}
]
}
]
}
}
Uncaught:
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: ObjectId('6ad390dcd79732f7a1fc9d30'),
details: {
operatorName: '$jsonSchema',
schemaRulesNotSatisfied: [
{
operatorName: 'properties',
propertiesNotSatisfied: [
{
propertyName: 'tag',
details: [
{
operatorName: 'uniqueItems',
specifiedAs: { uniqueItems: true },
reason: 'found a duplicate item',
consideredValue: [ 'sembako', 'sembako' ],
duplicatedValue: 'sembako'
}
]
}
]
}
]
}
}items sendiri menolak elemen yang salah tipe — tag berisi angka langsung ditolak, lengkap dengan posisi elemennya:
db.produk.insertOne({ nama: "Gula Pasir 1kg", sku: "MAK-001", harga: 15000, kategori: "Makanan", stok: 10, tag: ["sembako", 7] })Output yang Dihasilkan:
Uncaught:
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: ObjectId('6ad390dcd79732f7a1fc9d31'),
details: {
operatorName: '$jsonSchema',
schemaRulesNotSatisfied: [
{
operatorName: 'properties',
propertiesNotSatisfied: [
{
propertyName: 'tag',
details: [
{
operatorName: 'items',
reason: 'At least one item did not match the sub-schema',
itemIndex: 1,
details: [
{
operatorName: 'bsonType',
specifiedAs: { bsonType: 'string' },
reason: 'type did not match',
consideredValue: 7,
consideredType: 'int'
}
]
}
]
}
]
}
]
}
}additionalProperties dan Jebakan _id #
Secara default, field di luar properties bebas masuk — itu sebabnya merek bisa ditulis di Chapter-chapter sebelumnya. Kata kunci additionalProperties mengatur pintu itu: false menutupnya rapat, atau sebuah skema object membuat field asing tetap boleh asal mematuhi aturan. Kita coba versi false:
db.runCommand({ collMod: "produk", validator: { $jsonSchema: {
bsonType: "object",
required: ["nama", "sku", "harga", "kategori", "stok"],
properties: {
nama: { bsonType: "string", minLength: 3, maxLength: 60 },
sku: { bsonType: "string", pattern: "^[A-Z]{3}-[0-9]{3}$" },
harga: { bsonType: "int", minimum: 500, multipleOf: 500 },
kategori: { enum: ["Minuman", "Makanan", "Alat Tulis"] },
stok: { bsonType: "int", minimum: 0, maximum: 100000 },
tag: {
bsonType: "array",
items: { bsonType: "string" },
minItems: 1,
maxItems: 5,
uniqueItems: true
}
},
additionalProperties: false
} } }).ok
db.produk.insertOne({ nama: "Teh Melati 100g", sku: "MIN-002", harga: 15000, kategori: "Minuman", stok: 25, tag: ["teh"], merek: "Sari Wangi" })Output yang Dihasilkan:
1
Uncaught:
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: ObjectId('6ad390dcd79732f7a1fc9d32'),
details: {
operatorName: '$jsonSchema',
schemaRulesNotSatisfied: [
{
operatorName: 'additionalProperties',
specifiedAs: { additionalProperties: false },
additionalProperties: [ '_id', 'merek' ]
}
]
}
}Insert ditolak — tetapi perhatikan field yang dinyatakan melanggar: _id dan merek. Ternyata _id otomatis juga dihitung sebagai additional property karena tidak kita daftarkan di properties.
Kita perbaiki skemanya dengan mendaftarkan _id, lalu uji ulang:
db.runCommand({ collMod: "produk", validator: { $jsonSchema: {
bsonType: "object",
required: ["nama", "sku", "harga", "kategori", "stok"],
properties: {
_id: { bsonType: "objectId" },
nama: { bsonType: "string", minLength: 3, maxLength: 60 },
sku: { bsonType: "string", pattern: "^[A-Z]{3}-[0-9]{3}$" },
harga: { bsonType: "int", minimum: 500, multipleOf: 500 },
kategori: { enum: ["Minuman", "Makanan", "Alat Tulis"] },
stok: { bsonType: "int", minimum: 0, maximum: 100000 },
tag: {
bsonType: "array",
items: { bsonType: "string" },
minItems: 1,
maxItems: 5,
uniqueItems: true
}
},
additionalProperties: false
} } }).ok
db.produk.insertOne({ nama: "Teh Melati 100g", sku: "MIN-002", harga: 15000, kategori: "Minuman", stok: 25, tag: ["teh"], merek: "Sari Wangi" })
db.produk.insertOne({ nama: "Teh Melati 100g", sku: "MIN-002", harga: 15000, kategori: "Minuman", stok: 25, tag: ["teh"] })Output yang Dihasilkan:
1
Uncaught:
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: ObjectId('6ad390dcd79732f7a1fc9d33'),
details: {
operatorName: '$jsonSchema',
schemaRulesNotSatisfied: [
{
operatorName: 'additionalProperties',
specifiedAs: { additionalProperties: false },
additionalProperties: [ 'merek' ]
}
]
}
}
{
acknowledged: true,
insertedId: ObjectId('6ad390dcd79732f7a1fc9d34')
}Sekarang penolakannya bersih: hanya merek yang dianggap field asing, dan insert tanpa field asing lolos. Bentuk ketiga additionalProperties adalah skema object — field asing boleh hadir asal mematuhi aturan. Cocok untuk metadata bebas yang tetap ingin dibatasi:
db.runCommand({ collMod: "produk", validator: { $jsonSchema: {
bsonType: "object",
required: ["nama", "sku", "harga", "kategori", "stok"],
properties: {
_id: { bsonType: "objectId" },
nama: { bsonType: "string", minLength: 3, maxLength: 60 },
sku: { bsonType: "string", pattern: "^[A-Z]{3}-[0-9]{3}$" },
harga: { bsonType: "int", minimum: 500, multipleOf: 500 },
kategori: { enum: ["Minuman", "Makanan", "Alat Tulis"] },
stok: { bsonType: "int", minimum: 0, maximum: 100000 },
tag: {
bsonType: "array",
items: { bsonType: "string" },
minItems: 1,
maxItems: 5,
uniqueItems: true
}
},
additionalProperties: { bsonType: "string", maxLength: 30 }
} } }).ok
db.produk.insertOne({ nama: "Teh Melati 100g", sku: "MIN-002", harga: 15000, kategori: "Minuman", stok: 25, tag: ["teh"], merek: "Sari Wangi" })
db.produk.insertOne({ nama: "Teh Melati 100g", sku: "MIN-002", harga: 15000, kategori: "Minuman", stok: 25, tag: ["teh"], rating: 4.5 })Output yang Dihasilkan:
1
{
acknowledged: true,
insertedId: ObjectId('6ad390ddd79732f7a1fc9d35')
}
Uncaught:
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: ObjectId('6ad390ddd79732f7a1fc9d36'),
details: {
operatorName: '$jsonSchema',
schemaRulesNotSatisfied: [
{
operatorName: 'additionalProperties',
reason: 'at least one additional property did not match the subschema',
failingProperty: 'rating',
details: [
{
operatorName: 'bsonType',
specifiedAs: { bsonType: 'string' },
reason: 'type did not match',
consideredValue: 4.5,
consideredType: 'double'
}
]
}
]
}
}merek: "Sari Wangi" lolos karena string pendek; rating: 4.5 ditolak karena angka tidak memenuhi skema field asing. Perhatikan bahwa additionalProperties di root hanya mengatur field level atas — ia tidak menurun ke dalam embedded document.
Object Bersarang Harus Didefinisikan Eksplisit #
Validator tidak otomatis memeriksa isi embedded document. dimensi: { bsonType: "object" } saja akan menerima object isi apa pun — untuk menjaga isinya, kita tulis skema object bersarang lengkap dengan required dan additionalProperties-nya sendiri. Sekaligus kita tambahkan aturan bersyarat diskon dengan oneOf:
| Kata kunci | Makna |
|---|---|
allOf | Semua skema harus cocok |
anyOf | Minimal satu skema cocok |
oneOf | Tepat satu skema yang cocok |
not | Nilai tidak boleh cocok dengan skema |
db.runCommand({ collMod: "produk", validator: { $jsonSchema: {
bsonType: "object",
required: ["nama", "sku", "harga", "kategori", "stok"],
properties: {
_id: { bsonType: "objectId" },
nama: { bsonType: "string", minLength: 3, maxLength: 60 },
sku: { bsonType: "string", pattern: "^[A-Z]{3}-[0-9]{3}$" },
harga: { bsonType: "int", minimum: 500, multipleOf: 500 },
kategori: { enum: ["Minuman", "Makanan", "Alat Tulis"] },
stok: { bsonType: "int", minimum: 0, maximum: 100000 },
tag: {
bsonType: "array",
items: { bsonType: "string" },
minItems: 1,
maxItems: 5,
uniqueItems: true
},
dimensi: {
bsonType: "object",
required: ["panjang", "lebar", "tinggi"],
properties: {
panjang: { bsonType: "number", minimum: 0 },
lebar: { bsonType: "number", minimum: 0 },
tinggi: { bsonType: "number", minimum: 0 }
},
additionalProperties: false
},
diskon: {
oneOf: [
{
bsonType: "object",
required: ["persen"],
properties: { persen: { bsonType: "number", minimum: 0, maximum: 100 } }
},
{
bsonType: "object",
required: ["nominal"],
properties: { nominal: { bsonType: "int", minimum: 0, multipleOf: 500 } }
}
]
}
},
additionalProperties: { bsonType: "string", maxLength: 30 }
} } }).ok
db.produk.insertOne({ nama: "Kopi Gayo 250g", sku: "MIN-001", harga: 85000, kategori: "Minuman", stok: 40, tag: ["kopi", "arabika"], dimensi: { panjang: 10, lebar: 6, tinggi: 18 }, diskon: { persen: 10 } })Output yang Dihasilkan:
1
{
acknowledged: true,
insertedId: ObjectId('6ad390ddd79732f7a1fc9d37')
}Skema diskon membaca: diskon boleh berupa object dengan persen (0–100) atau object dengan nominal (kelipatan 500) — tepat satu, tidak keduanya. Tiga percobaan berikut menguji sisi gagalnya:
db.produk.insertOne({ nama: "Kopi Gayo 250g", sku: "MIN-001", harga: 85000, kategori: "Minuman", stok: 40, tag: ["kopi"], dimensi: { panjang: 10, lebar: 6, tinggi: 18, berat: 250 } })
db.produk.insertOne({ nama: "Kopi Gayo 250g", sku: "MIN-001", harga: 85000, kategori: "Minuman", stok: 40, tag: ["kopi"], diskon: { persen: 10, nominal: 5000 } })
db.produk.insertOne({ nama: "Kopi Gayo 250g", sku: "MIN-001", harga: 85000, kategori: "Minuman", stok: 40, tag: ["kopi"], diskon: { persen: 150 } })Output yang Dihasilkan:
Uncaught:
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: ObjectId('6ad390ddd79732f7a1fc9d38'),
details: {
operatorName: '$jsonSchema',
schemaRulesNotSatisfied: [
{
operatorName: 'properties',
propertiesNotSatisfied: [
{
propertyName: 'dimensi',
details: [
{
operatorName: 'additionalProperties',
specifiedAs: { additionalProperties: false },
additionalProperties: [ 'berat' ]
}
]
}
]
}
]
}
}
Uncaught:
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: ObjectId('6ad390ddd79732f7a1fc9d39'),
details: {
operatorName: '$jsonSchema',
schemaRulesNotSatisfied: [
{
operatorName: 'properties',
propertiesNotSatisfied: [
{
propertyName: 'diskon',
details: [
{
operatorName: 'oneOf',
reason: 'more than one subschema matched',
matchingSchemaIndexes: [ 0, 1 ]
}
]
}
]
}
]
}
}
Uncaught:
MongoServerError: Document failed validation
Additional information: {
failingDocumentId: ObjectId('6ad390ddd79732f7a1fc9d3a'),
details: {
operatorName: '$jsonSchema',
schemaRulesNotSatisfied: [
{
operatorName: 'properties',
propertiesNotSatisfied: [
{
propertyName: 'diskon',
details: [
{
operatorName: 'oneOf',
schemasNotSatisfied: [
{
index: 0,
details: [
{
operatorName: 'properties',
propertiesNotSatisfied: [
{
propertyName: 'persen',
details: [
{
operatorName: 'maximum',
specifiedAs: { maximum: 100 },
reason: 'comparison failed',
consideredValue: 150
}
]
}
]
}
]
},
{
index: 1,
details: [
{
operatorName: 'required',
specifiedAs: { required: [ 'nominal' ] },
missingProperties: [ 'nominal' ]
}
]
}
]
}
]
}
]
}
]
}
}Bersihkan database latihan:
db.dropDatabase()Output yang Dihasilkan:
{ ok: 1, dropped: 'latihan_validasi' }Rangkuman #
- Satu field boleh menggabungkan banyak aturan:
namamemakaibsonType+minLength+maxLengthsekaligus. - Array dijaga lewat
items(skema per elemen),minItems/maxItems, danuniqueItems. additionalProperties: falsemenutup field asing; bentuk skema object membatasi field asing tanpa menutupnya._iddihitung sebagai field biasa olehadditionalProperties— daftarkan dipropertiesagar insert sah tidak ikut ditolak.- Embedded document hanya divalidasi bila skemanya ditulis eksplisit sebagai object bersarang.
allOf/anyOf/oneOf/notmenyusun aturan bersyarat;oneOfmenuntut tepat satu skema cocok.
Dua kenop terakhir mengatur bagaimana validator diterapkan: validationLevel menentukan cakupan pemeriksaan, dan validationAction menentukan nasib document yang gagal. Kita bahas keduanya di dua Chapter berikutnya.