BSON dan tipe

Mengenal BSON sebagai format penyimpanan biner beserta tipe data penting seperti ObjectId, tanggal, dan desimal.

Daftar isi

Format dokumen MongoDB meniru keterbacaan JSON, tetapi server tidak menyimpan dokumen sebagai teks JSON. Format penyimpanannya adalah BSON, yaitu pengodean biner dari dokumen yang menambah sejumlah tipe data. Topik ini memperkenalkan BSON beserta tabel tipe yang sering dipakai, lalu membahas tiga tipe yang paling menentukan, yaitu ObjectId, tanggal, dan desimal. Pembahasan ditutup dengan arti ketepatan tipe saat query.

Dari JSON ke BSON#

JSON hanya mengenal enam tipe nilai, yaitu string, number, boolean, null, object, dan array. Kebutuhan nyata aplikasi melebihi enam tipe itu, misalnya waktu dengan presisi milidetik, bilangan bulat besar, dan angka desimal yang wajib eksak. Bila dipaksa ke JSON, tipe-tipe tersebut harus disimpan sebagai string lalu ditafsirkan ulang oleh aplikasi, dan server tidak lagi dapat membedakan tipenya.

MongoDB memecahkan masalah itu dengan BSON (Binary JSON), yaitu format biner untuk merepresentasikan dokumen. Setiap field di dalam dokumen BSON membawa penanda tipe, sehingga server mengetahui apakah sebuah nilai berupa teks, tanggal, atau bilangan tanpa menafsirkan ulang isinya. Format biner juga memungkinkan server melompat langsung ke field tertentu tanpa membaca seluruh dokumen. Saat mengetik perintah di mongosh, dokumen masih terlihat seperti JSON, sedangkan driver mengubahnya ke BSON sebelum dikirim ke server.

Tabel berikut memuat tipe BSON yang paling sering dijumpai sehari-hari, bukan seluruh daftar tipe BSON. Angka pada kolom kode tipe disebut type number dalam spesifikasi BSON, dan inilah penanda yang tersimpan di dalam biner untuk setiap field.

Tipe BSONKode tipeContoh penulisanKegunaan
double120.5bilangan pecahan
string2“Ali”teks
embedded document3{ kota: “Bandung” }objek bersarang
array4[ “BD1”, “PW2” ]deretan nilai
ObjectId7ObjectId(’…')nilai default field _id
boolean8truenilai logika
date9new Date(“2026-03-15”)waktu UTC
null10nullnilai kosong yang disengaja
32-bit integer16Int32(20)bilangan bulat kecil
64-bit integer18Long(20)bilangan bulat besar
Decimal12819NumberDecimal(“0.1”)angka berpresisi tinggi

Kode tipe di dalam biner BSON#

Bentuk biner BSON menyimpan panjang total dokumen pada empat byte pertama, lalu untuk setiap field menyimpan kode tipe, nama field, dan nilainya. Dengan susunan itu, byte kelima dari sebuah dokumen, yaitu indeks 4, memuat kode tipe field pertamanya. Driver Python PyMongo yang terpasang di lingkungan latihan menyediakan fungsi encode untuk mengubah dokumen ke bentuk biner BSON. Skrip berikut mencetak kode tipe dari byte tersebut untuk sebelas contoh nilai.

python kode-tipe-bson.py
from bson import Decimal128, Int64, ObjectId, encode
from datetime import datetime

contoh = [
    ("string", {"a": "teks"}),
    ("double", {"a": 20.5}),
    ("int32", {"a": 20}),
    ("int64", {"a": Int64(20)}),
    ("date", {"a": datetime(2026, 3, 15)}),
    ("ObjectId", {"a": ObjectId()}),
    ("Decimal128", {"a": Decimal128("0.1")}),
    ("boolean", {"a": True}),
    ("dokumen", {"a": {"b": 1}}),
    ("array", {"a": [1, 2]}),
    ("null", {"a": None}),
]

for nama, dok in contoh:
    # byte pada indeks 4 memuat kode tipe field pertama
    print(f"{nama:10} {encode(dok)[4]}")
text Keluaran
string     2
double     1
int32      16
int64      18
date       9
ObjectId   7
Decimal128 19
boolean    8
dokumen    3
array      4
null       10

Keluaran memperlihatkan bahwa angka pecahan dikodekan sebagai double dengan kode 1, sedangkan bilangan bulat dikodekan sebagai int32 dengan kode 16 atau int64 dengan kode 18 menurut rentang nilainya. Tipe date memakai kode 9, sedangkan Decimal128 memakai kode 19. Perbedaan inilah inti dari ketepatan tipe, yaitu server menyimpan dan membedakan nilai menurut tipenya, bukan hanya menurut teks yang tampil di layar.

ObjectId#

Tipe ObjectId menjadi nilai default untuk field _id. Nilainya terdiri atas 12 byte yang ditulis sebagai 24 karakter heksadesimal. Empat byte pertama menyimpan jumlah detik sejak epoch, yaitu titik nol waktu pada 1 Januari 1970. Lima byte berikutnya berupa nilai acak, sedangkan tiga byte terakhir berupa penghitung naik. Komposisi itu membuat ObjectId unik tanpa koordinasi dengan server, karena dua mesin berbeda hampir tidak mungkin menghasilkan nilai sama pada detik yang sama. ObjectId dibuat di sisi klien saat dokumen disisipkan tanpa _id, dan metode getTimestamp mengembalikan tanggal pembuatannya.

mongosh
use akademik
const idBaru = new ObjectId()
idBaru
idBaru.getTimestamp()
text Keluaran
switched to db akademik

ObjectId('6ae86103f9ef711d10ce5f47')
ISODate('2026-11-02T10:08:03.000Z')

Nilai ISODate yang tercetak dibaca dari empat byte pertama, sehingga waktunya bertepatan dengan saat ObjectId itu dibuat. Sisa byte-nya menjamin keunikan tanpa perlu server menomori dokumen satu per satu. Bila dokumen memang membawa _id sendiri, server memakai nilai tersebut dan ObjectId tidak dihasilkan.

Tanggal#

Tipe date menyimpan jumlah milidetik sejak epoch dalam waktu UTC. Dokumen tidak menyimpan nama zona waktu, sehingga semua tanggal tersimpan dalam satu acuan yang sama dan perbandingan antartanggal menjadi sederhana. mongosh menampilkan nilai date sebagai ISODate yang diakhiri huruf Z, yaitu penanda waktu UTC, sedangkan konversi ke zona waktu lokal menjadi tugas aplikasi yang membaca data.

mongosh
db.kalender.insertOne({ acara: "Ujian tengah semester", mulai: new Date("2026-03-15T01:00:00Z") })
db.kalender.findOne()
text Keluaran
{
  acknowledged: true,
  insertedId: ObjectId('6ae86103f9ef711d10ce5f48')
}
{
  _id: ObjectId('6ae86103f9ef711d10ce5f48'),
  acara: 'Ujian tengah semester',
  mulai: ISODate('2026-03-15T01:00:00.000Z')
}

String yang diberikan ke new Date memuat penanda Z, dan keluaran menampilkan waktu persis sebagaimana disimpan, yaitu pukul 01:00 UTC. Penulisan tanggal lewat string seperti ini juga menghindarkan salah baca urutan hari dan bulan karena formatnya lengkap sampai detik. Bila argumen string dihilangkan, new Date mengambil waktu saat itu dari mesin yang menjalankan mongosh.

Desimal untuk angka presisi#

Tipe double adalah bilangan pecahan IEEE 754 64-bit dan menjadi tipe default untuk setiap angka pecahan yang diketik di mongosh, sedangkan angka bulat dikirim sebagai integer. Format double itu cepat, tetapi sebagian nilai desimal tidak terwakili persis dalam biner, sehingga hasil perhitungannya menyisakan beda kecil. Salah satu contohnya adalah 0.1 ditambah 0.2 yang hasilnya bukan 0.3 persis. Untuk nilai yang wajib eksak seperti saldo dan harga, BSON menyediakan tipe Decimal128 yang menghitung dalam basis sepuluh.

mongosh
db.angka.insertMany([ { label: "double", a: 0.1, b: 0.2 }, { label: "desimal", a: NumberDecimal("0.1"), b: NumberDecimal("0.2") } ])
db.angka.aggregate([{ $addFields: { jumlah: { $add: ["$a", "$b"] } } }])
text Keluaran
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ae86103f9ef711d10ce5f49'),
    '1': ObjectId('6ae86103f9ef711d10ce5f4a')
  }
}
[
  {
    _id: ObjectId('6ae86103f9ef711d10ce5f49'),
    label: 'double',
    a: 0.1,
    b: 0.2,
    jumlah: 0.30000000000000004
  },
  {
    _id: ObjectId('6ae86103f9ef711d10ce5f4a'),
    label: 'desimal',
    a: Decimal128('0.1'),
    b: Decimal128('0.2'),
    jumlah: Decimal128('0.3')
  }
]

Tahap $addFields menambahkan field jumlah yang dihitung server dengan operator $add. Baris double menghasilkan 0.30000000000000004, sedangkan baris desimal menghasilkan Decimal128('0.3') yang eksak. Nilai NumberDecimal ditulis dari string seperti NumberDecimal("0.1") agar angkanya tidak melewati double lebih dahulu sebelum menjadi desimal.

Ketepatan tipe saat query#

Dua field yang sama-sama tampil sebagai angka 20 pada keluaran bisa jadi bertipe penyimpanan berbeda. mongosh menuliskan angka bulat sebagai integer secara default. Untuk memilih tipe lain secara eksplisit, gunakan Double untuk double, Int32 untuk 32-bit integer, dan Long untuk 64-bit integer. Query perbandingan biasa tetap mencocokkan nilai lintas tipe numerik, tetapi operator $type menyaring menurut tipe tersimpannya. Perbedaan ini penting ketika aplikasi bergantung pada batas tipe, misalnya bilangan bulat yang tidak boleh kehilangan ketelitiannya.

mongosh
db.pengukuran.insertOne({ suhu: Double(20), tekanan: Int32(20), cadangan: Long(20) })
db.pengukuran.find({ suhu: { $type: "double" } })
db.pengukuran.find({ tekanan: { $type: "int" } })
text Keluaran
{
  acknowledged: true,
  insertedId: ObjectId('6ae86103f9ef711d10ce5f4b')
}
[
  {
    _id: ObjectId('6ae86103f9ef711d10ce5f4b'),
    suhu: 20,
    tekanan: 20,
    cadangan: Long('20')
  }
]
[
  {
    _id: ObjectId('6ae86103f9ef711d10ce5f4b'),
    suhu: 20,
    tekanan: 20,
    cadangan: Long('20')
  }
]

Keluaran menampilkan ketiga field sebagai angka 20 yang mirip, kecuali cadangan yang tampil sebagai Long('20'). Query dengan $type "double" hanya menemukan dokumen melalui field suhu karena nilainya ditulis dengan Double, sedangkan query $type "int" hanya menemukan dokumen melalui field tekanan yang ditulis dengan Int32. Perbedaan tipe itu tidak terlihat pada tampilan, dan hanya $type yang membukanya.

Ringkasan#

  • Server MongoDB menyimpan dokumen sebagai BSON, yaitu format biner yang membawa penanda tipe untuk setiap field.
  • ObjectId adalah nilai default _id yang unik, dan empat byte pertamanya menyimpan waktu pembuatan yang dibaca oleh getTimestamp.
  • Tipe date menyimpan milidetik UTC tanpa zona waktu, sedangkan Decimal128 menyimpan angka desimal secara eksak untuk kebutuhan seperti uang.
  • mongosh menuliskan angka bulat sebagai integer dan angka pecahan sebagai double, sedangkan operator $type menyaring menurut tipe tersimpannya.

Referensi#