JSON dan serialisasi

Modul json untuk parsing dan serialisasi data serta marshalling objek.

Daftar isi

Pertukaran data antar program hampir selalu melewati dua jalan yang sama, yaitu format teks yang disepakati bersama pihak lain, atau lapisan serialisasi yang menyimpan objek langsung. Nim menyediakan keduanya di pustaka standar, yaitu modul json untuk dokumen JSON yang dibaca dan ditulis secara eksplisit, serta modul marshal untuk mengubah objek Nim menjadi teks dan sebaliknya tanpa deklarasi tambahan. Topik ini membawa keduanya pada kasus yang sering dijumpai, yaitu mencatat bacaan sensor ke berkas JSON lalu membacanya kembali, dan mengarsipkan sekumpulan objek hasil ujian.

Modul json mewakili dokumen JSON sebagai tipe JsonNode yang dapat berupa objek, larik, string, angka, maupun boolean. Operator %* membangun node dari nilai Nim dengan notasi yang mirip JSON itu sendiri, sehingga struktur dokumen terbaca hampir seperti berkasnya. Program berikut mencatat dua sensor ke berkas catatan.json, membacanya kembali dengan parseJson, lalu menelusuri larik sensor untuk mencetak setiap bacaan.

nim catatan_sensor.nim
# catatan_sensor.nim: menulis lalu membaca kembali berkas JSON

import json

type SensorSuhu = object
  nama: string
  suhu: float

let sensor = [
  SensorSuhu(nama: "ruang-server", suhu: 27.5),
  SensorSuhu(nama: "ruang-laboratorium", suhu: 24.0),
]

var dokumen = %*{
  "perangkat": "pencatat-suhu",
  "waktu": "2026-11-04T08:00:00",
  "sensor": []
}
for s in sensor:
  dokumen["sensor"].add(%*{"nama": s.nama, "suhu": s.suhu})

writeFile("catatan.json", pretty(dokumen))
echo "tercatat: ", len(dokumen["sensor"]), " sensor"

let bacaan = parseJson(readFile("catatan.json"))
echo "perangkat: ", bacaan["perangkat"].getStr()
for s in bacaan["sensor"]:
  echo s["nama"].getStr(), " -> ", s["suhu"].getFloat(), " derajat"
bash
# kompilasi dan jalankan program catatan sensor JSON
nim c --hints:off -r catatan_sensor.nim
text Keluaran
tercatat: 2 sensor
perangkat: pencatat-suhu
ruang-server -> 27.5 derajat
ruang-laboratorium -> 24.0 derajat

Berkas catatan.json dihasilkan dengan pretty, yaitu pencetak yang menambah indentasi agar dokumen nyaman dibaca manusia, dan berikut isi lengkapnya.

json Isi berkas catatan.json
{
  "perangkat": "pencatat-suhu",
  "waktu": "2026-11-04T08:00:00",
  "sensor": [
    {
      "nama": "ruang-server",
      "suhu": 27.5
    },
    {
      "nama": "ruang-laboratorium",
      "suhu": 24.0
    }
  ]
}

Setiap node membawa kind yang menyatakan wajahnya, dengan nilai seperti JObject, JArray, JString, dan JFloat, sehingga kode yang menghadapi dokumen dari luar dapat memeriksa bentuknya lebih dahulu sebelum mengambil isinya. Pemeriksaan ini menjadi penting ketika dokumen berasal dari pihak lain, karena bidang yang dijanjikan bisa saja tidak hadir atau hadir dalam wajah yang berbeda, dan pemeriksaan kind mengubah kemungkinan itu menjadi percabangan yang tenang.

Akses bidang memakai tanda kurung siku dengan indeks string untuk objek dan bilangan untuk larik, lalu diakhiri pengambil bertipe seperti getStr dan getFloat Pengambil bertipe ini yang menjaga keamanan data, karena node yang seharusnya angka tetapi berisi teks akan memunculkan error yang jelas saat program berjalan, bukan nilai kosong yang berjalan diam.

Selain penelusuran manual, modul json juga memetakan node ke objek Nim yang sudah dinyatakan dengan type melalui proc to, misalnya to[seq[SensorSuhu]](bacaan["sensor"]) menghasilkan kumpulan objek lengkap dengan bidangnya tanpa satu pun pengambil bertipe. Arah sebaliknya dilayani makro %* yang menerima objek apa pun, sehingga dokumen dan objek saling mengisi tanpa salin-tempel bidang. Dokumen yang rusak dikenali saat penguraian, karena parseJson melemparkan JsonParsingError yang menyebut posisi kesalahannya, dan blok try di sekitar penguraian menjadi tempat yang tepat untuk pesan kesalahan yang ramah bagi pemakai.

Penulisan tanpa notasi %* juga tersedia untuk pembangunan bertahap, yaitu newJObject menciptakan objek kosong dan newJArray menciptakan larik kosong, lalu keduanya diisi dengan add dan penugasan indeks. Cara ini cocok ketika struktur dokumen baru diketahui di tengah perulangan, misalnya satu entri per baris berkas sumber, dan hasil akhirnya tetap JsonNode yang dapat dicetak, diuraikan ulang, atau ditulis ke berkas.

Serialisasi objek dengan marshal#

Modul marshal bekerja pada tingkat yang berbeda, yaitu objek Nim yang dinyatakan dengan type. Operator $$ mengubah nilai apa pun yang tersusun atas tipe standar menjadi representasi teks yang juga berbentuk JSON, sedangkan to[T] menguraikannya kembali menjadi objek bertipe tanpa penulisan pengambil bidang satu per satu. Tipe dan bentuk data diperiksa pada saat penguraian, sehingga kesalahan bentuk terdeteksi lebih dini.

nim arsip_marshal.nim
# arsip_marshal.nim: serialisasi objek Nim dengan modul marshal

import marshal

type NilaiUjian = object
  nama: string
  skor: int

let data = @[
  NilaiUjian(nama: "Ali", skor: 88),
  NilaiUjian(nama: "Citra", skor: 91),
]

let teks = $$data
echo teks

let bacaan = to[seq[NilaiUjian]](teks)
for n in bacaan:
  echo n.nama, ": ", n.skor
bash
# kompilasi dan jalankan program arsip marshal
nim c --hints:off -r arsip_marshal.nim
text Keluaran
[{"nama": "Ali", "skor": 88}, {"nama": "Citra", "skor": 91}]
Ali: 88
Citra: 91

Bentuk teks hasil $$ padat tanpa spasi karena ditujukan bagi mesin, sedangkan $ menghasilkan representasi satu baris dan pretty menambah indentasi. Ketiganya berbagi satu pengurai yang sama, sehingga arsip yang ditulis dengan salah satu tetap terbaca oleh to[T] tanpa penyetelan.

Perbandingan keduanya menegaskan pembagian tugasnya. Modul json menuntut dokumen dibangun bidang demi bidang dengan %* dan dibaca dengan pengambil bertipe, tetapi sebagai imbalannya menerima JSON dari pihak mana pun, termasuk layanan web dan berkas konfigurasi milik perangkat lunak lain. Modul marshal hampir tanpa upaya karena objek langsung berubah menjadi teks, namun format hasilnya ditujukan untuk program Nim, sehingga tidak dijanjikan sebagai antarmuka untuk bahasa lain. Pembagian ini membuat keputusan bentuk data berpindah ke saat desain, yaitu format bersama dipikirkan matang di awal, sedangkan arsip internal dibiarkan mengikuti tipe program, dan keduanya tidak perlu saling menunggu.

Kesalahan umum#

  • Menganggap hasil parseJson bertipe data langsung. Nilai bacaan["sensor"] tetap JsonNode, sehingga echo bacaan["sensor"] mencetak representasi JSON-nya, bukan isi lariknya, dan penelusuran dengan for baru menghasilkan elemen per elemen.
  • Memanggil pengambil bertipe yang tidak cocok, misalnya getStr pada node angka. Program berhenti dengan error yang menyatakan ketidakcocokan tipe pada posisi pemanggilan, dan cara amannya memeriksa kind terlebih dahulu bila bentuk dokumen belum pasti.
  • Menggunakan marshal sebagai format pertukaran dengan pihak lain. Representasi $$ berikatan dengan tipe Nim, sehingga perubahan kecil pada type mengubah teksnya, dan pemakai luar yang mengandalkan bentuk itu akan rusak tanpa peringatan.

Ringkasan#

  • Modul json mewakili dokumen sebagai JsonNode, dibangun dengan %*, ditulis dengan pretty, dibaca dengan parseJson, dan ditelusuri dengan indeks disertai pengambil bertipe seperti getStr dan getFloat.
  • Modul marshal mengubah objek Nim menjadi teks dengan $$ dan menguraikannya kembali dengan to[T], sehingga arsip objek tidak memerlukan penulisan bidang satu per satu.
  • Pilihan modul mengikuti mitra data, yaitu json untuk format lintas bahasa yang disepakati bersama, dan marshal untuk kebutuhan internal program Nim.

Referensi#