Laporan mingguan untuk tim bisnis, data umpan untuk sistem ETL, atau migrasi dari spreadsheet — semua skenario itu butuh data MongoDB dalam bentuk yang bisa dibaca manusia dan aplikasi umum: JSON atau CSV. Untuk kebutuhan itulah mongoexport dan mongoimport dibuat. Keduanya sering disalahpahami sebagai alternatif backup, padahal perannya berbeda: ini alat pertukaran data, dan format yang mereka pegang kehilangan sebagian informasi tipe BSON. Chapter ini membedah format ekspornya, membuktikan kehilangan tipe secara nyata, lalu menutup dengan opsi impor untuk menangani data yang sudah ada.

Peta materi Chapter ini:

  • Ekspor JSONL dan CSV: --fields, --type=csv, --jsonArray, --jsonFormat.
  • Anatomi kehilangan tipe BSON lewat Extended JSON — diuji langsung di mongosh.
  • Impor: --headerline untuk CSV dan empat --mode penanganan duplikat.

Ekspor ke JSON dan CSV #

Sama seperti pasangan mongodump, kedua tool ini tinggal di paket MongoDB Database Tools dan dijalankan dari terminal sistem. Perintah-perintah berikut mengikuti dokumentasi resmi (lingkungan latihan kita tidak memasang paket tersebut, sehingga tidak ada klaim keluaran eksekusi):

bash
  # ekspor JSON — default: satu document per baris (JSON Lines)
mongoexport --uri="mongodb://127.0.0.1:27017/?directConnection=true" \
  --db=latihan_backup --collection=buku --out=buku.jsonl

  # ekspor CSV — fields wajib dinyatakan satu per satu
mongoexport --uri="mongodb://127.0.0.1:27017/?directConnection=true" \
  --db=latihan_backup --collection=buku --type=csv \
  --fields=judul,harga,stok --out=buku.csv

  # ekspor sebagai satu array JSON (bukan per baris)
mongoexport --uri="mongodb://127.0.0.1:27017/?directConnection=true" \
  --db=latihan_backup --collection=buku --jsonArray --out=buku_array.json

Berkas JSONL hasil ekspor berbentuk seperti ini (ilustrasi format, bukan hasil eksekusi):

text
{"_id":1,"judul":"Bumi","harga":89000,"stok":1200000,"terbit":{"$date":"2015-01-01T00:00:00Z"},"penulis":"Tere Liye"}
{"_id":2,"judul":"Laut Bercerita","harga":98000,"stok":500000,"terbit":{"$date":"2016-10-01T00:00:00Z"},"penulis":"Leila S. Chudori"}

Sedangkan CSV membuka baris pertama sebagai nama kolom, lalu menuliskan nilai apa adanya sebagai teks (ilustrasi):

text
judul,harga,stok
Bumi,89000,1200000
Laut Bercerita,98000,500000

Opsi-opsi yang paling sering terpakai:

OpsiBerlaku untukPeran
--type=csv (default json)keduanyaMemilih format keluaran; CSV wajib --fields atau --fieldFile
--fields=nama,umurkeduanyaMemilih kolom/field yang diekspor
--jsonArrayJSONSeluruh hasil menjadi satu array, bukan satu document per baris
--jsonFormat=canonicalJSONExtended JSON v2 mode canonical (default relaxed)
--query, --sort, --limitkeduanyaMenyaring dan mengurutkan document yang diekspor
--prettyJSONKeluaran berformat indentasi

Tipe BSON di Mata JSON #

Inilah bagian yang membuat pasangan ini bukan alat backup. BSON punya tipe yang tidak ada di JSON: Date, int 32-bit, long 64-bit, Decimal128, ObjectId, hingga data biner. Mari siapkan satu document dengan tipe-tipe tersebut:

js
use latihan_backup
db.dropDatabase()
db.buku.insertOne({
  _id: 1, judul: "Bumi", harga: 89000, stok: Long("1200000"),
  terbit: ISODate("2015-01-01T00:00:00Z"), penulis: "Tere Liye"
})
db.buku.findOne()

Output yang Dihasilkan:

text
switched to db latihan_backup
{ ok: 1, dropped: 'latihan_backup' }
{ acknowledged: true, insertedId: 1 }
{
  _id: 1,
  judul: 'Bumi',
  harga: 89000,
  stok: Long('1200000'),
  terbit: ISODate('2015-01-01T00:00:00.000Z'),
  penulis: 'Tere Liye'
}

mongoexport menuliskan document dalam format Extended JSON v2 — dan kita bisa melihat persis bentuknya lewat EJSON.stringify() di mongosh. Mode relaxed (default mongoexport) versus canonical:

js
EJSON.stringify(db.buku.findOne(), null, 2)
EJSON.stringify(db.buku.findOne(), null, 2, { relaxed: false })

Output yang Dihasilkan:

text
{
  "_id": 1,
  "judul": "Bumi",
  "harga": 89000,
  "stok": 1200000,
  "terbit": {
    "$date": "2015-01-01T00:00:00Z"
  },
  "penulis": "Tere Liye"
}
{
  "_id": {
    "$numberInt": "1"
  },
  "judul": "Bumi",
  "harga": {
    "$numberInt": "89000"
  },
  "stok": {
    "$numberLong": "1200000"
  },
  "terbit": {
    "$date": {
      "$numberLong": "1420070400000"
    }
  },
  "penulis": "Tere Liye"
}

Perbedaan dua mode ini tepat di titik angka. Mode relaxed menulis stok: 1200000 sebagai angka polos — long dan int tidak bisa dibedakan lagi. Mode canonical menjaga setiap tipe lewat pembungkus $numberInt dan $numberLong. Date bertahan di kedua mode lewat $date, tetapi datanya kembali sebagai angka epoch di mode canonical. Sebagai catatan: pembungkus $date hanya hidup selama file diproses oleh tool yang memahami Extended JSON — mongoimport memahaminya, spreadsheet dan parser JSON biasa tidak.

Apa yang terjadi kalau file itu diproses sebagai JSON polos lalu dimasukkan kembali? Simulasikan dengan JSON.parse dan bandingkan tipenya:

js
db.bukuJson.insertOne(JSON.parse(JSON.stringify(db.buku.findOne())))
db.bukuJson.aggregate([
  { $project: {
      hargaType: { $type: "$harga" },
      stokType: { $type: "$stok" },
      terbitType: { $type: "$terbit" }
  } }
])

Output yang Dihasilkan:

text
{ acknowledged: true, insertedId: 1 }
[ { _id: 1, hargaType: 'int', stokType: 'object', terbitType: 'string' } ]

Kerusakannya nyata: terbit yang semula date menjadi string, dan stok yang semula long berubah menjadi subdocument berisi representasi internal angka 64-bit (high, low, unsigned). Hanya harga yang selamat karena kebetulan muat sebagai angka bulat kecil. Pada CSV kerusakan ini lebih total lagi — semua nilai menjadi teks, tanpa kecuali, kecuali kita mendeklarasikan tipe per kolom lewat --columnsHaveTypes saat impor.

Impor Kembali ke MongoDB #

mongoimport membaca berkas Extended JSON, CSV, atau TSV — bukan hanya buatan mongoexport, tetapi juga tool pihak ketiga:

bash
  # impor JSONL ke collection baru
mongoimport --uri="mongodb://127.0.0.1:27017/?directConnection=true" \
  --db=latihan_staging --collection=buku --file=buku.jsonl

  # impor CSV: baris pertama berisi nama kolom
mongoimport --uri="mongodb://127.0.0.1:27017/?directConnection=true" \
  --db=latihan_staging --collection=buku_csv \
  --type=csv --headerline --file=buku.csv

  # timpa document yang sudah ada berdasarkan _id, sisanya disisipkan
mongoimport --uri="mongodb://127.0.0.1:27017/?directConnection=true" \
  --db=latihan_staging --collection=buku \
  --mode=upsert --upsertFields=_id --file=buku.jsonl

Opsi --mode menentukan nasib document yang _id-nya sudah ada di target — dan --upsertFields mengubah kunci pencocokannya bila _id tidak cocok dipakai:

ModePerilaku pada document yang cocok
insert (default)Galat duplikat _id; seluruh impor berhenti
upsertMenimpa document lama dengan versi dari file
mergeMenggabungkan field lama dan baru menjadi satu document
deleteMenghapus document lama yang cocok

Untuk data hasil mongoexport, gunakan versi mongoimport yang sama atau lebih baru; data lawas format Extended JSON v1 perlu bendera --legacy.

Rangkuman #

  • mongoexport menghasilkan JSONL (satu document per baris), array tunggal (--jsonArray), atau CSV (--type=csv + --fields); mongoimport membaca semuanya kembali.
  • JSON keluaran memakai Extended JSON v2 — mode relaxed (default) meratakan perbedaan int/long, mode canonical menjaga semuanya lewat $numberInt/$numberLong/$date.
  • Round-trip melalui JSON polos atau CSV merusak tipe: Date menjadi string, long menjadi subdocument; CSV membuat semua nilai menjadi teks.
  • --mode insert|upsert|merge|delete plus --upsertFields mengatur penanganan duplikat saat impor.
  • Untuk backup penuh, tetap gunakan mongodump — mongoexport adalah jembatan data antar sistem, bukan salinan keselamatan.