Jebakan decimal FHIR di JavaScript
Lesson terakhir modul Bulk Data: kenapa 36.50 bisa berubah jadi 36.5, dan cara mempertahankan presisinya
Sebelumnya, di lesson 0015, setiap baris NDJSON di-JSON.parse lalu di-insert per batch.
Di lesson 0014 Anda sudah melihat "value":36.50 di contoh Observation; lesson ini menjelaskan apa yang terjadi pada angka itu.
JSON.parse mengubah 2.00 jadi 2,
kenapa itu masalah untuk decimal FHIR, cara mempertahankan teks asli angka (reviver context.source, kolom NUMERIC),
dan cara mengirimnya kembali sebagai JSON number tanpa kehilangan nol (JSON.rawJSON).
1. Jebakan khas FHIR: decimal di JavaScript
Di FHIR, presisi angka adalah bagian dari data: "0.010 is regarded as different to 0.01, and the original precision should be preserved", dan
"Implementations SHALL handle decimal values in ways that preserve and respect the precision of the value as represented for presentation purposes". [1]
Di JSON, decimal ditulis sebagai JSON number, dan spesifikasi JSON FHIR sendiri memperingatkan:
"When using a JavaScript JSON.parse() implementation, note that JavaScript natively supports only one numeric datatype ... trailing 0s after a decimal point will be lost e.g. 2.00 will be converted to 2."
Sarannya: "consider using a custom parser and big number library". [2]
JSON.parse('{"value":36.50}').value // 36.5 → "36.50 °C" berubah jadi "36.5 °C" di UI
JSON.parse('{"value":0.010}').value // 0.01 → presisi alat ukur hilang
// Node 24: reviver menerima context.source, teks asli nilai primitif
JSON.parse('{"value":36.50}', (key, value, context) =>
key === "value" ? context.source : value) // { value: "36.50" } → simpan sebagai string / NUMERIC
Hasil di atas saya jalankan di Node 24.21.0. context.source berisi "The original JSON string representing this value" dan hanya ada untuk nilai primitif;
MDN juga mengingatkan bahwa reviver berjalan setelah angka dikonversi, jadi nilainya "may lose precision in the process". [3]
Simpan ke database sebagai tipe decimal (mis. NUMERIC di PostgreSQL), bukan float. Kabar baiknya, FHIR sendiri mencatat "large and/or highly precise values are extremely rare in medicine"; masalah yang umum adalah nol di belakang koma. [1]
Teks asli sudah aman di database. Masalah berikutnya muncul saat resource itu dikirim kembali ke server FHIR.
2. Kirim kembali tanpa kehilangan nol: JSON.rawJSON
Representasi JSON untuk decimal adalah "A JSON number". [1]
Jadi string "36.50" dari reviver tidak boleh langsung di-JSON.stringify: hasilnya JSON string, bukan number.
Mengubahnya dulu ke Number juga tidak menolong, karena nol di belakang koma hilang lagi.
JSON.stringify({ value: "36.50" }) // {"value":"36.50"} → JSON string, bukan decimal FHIR
JSON.stringify({ value: Number("36.50") }) // {"value":36.5} → nol hilang lagi
JSON.stringify({ value: JSON.rawJSON("36.50") }) // {"value":36.50} → JSON number, presisi utuh
JSON.rawJSON() "creates a "raw JSON" object containing a piece of JSON text. When serialized to JSON, the raw JSON object is treated as if it is already a piece of JSON."
MDN menyebut kegunaan utamanya: "The most typical use case is the floating point number loss of precision problem."
Objek yang dihasilkan "has null prototype and is frozen" dan menyimpan teks aslinya di properti rawJSON; JSON.rawJSON melempar SyntaxError kalau teksnya "not valid JSON, or if it represents an object or array".
Status MDN: Baseline 2025. [4]
Digabung dengan reviver, satu baris NDJSON bisa dibaca dan ditulis ulang tanpa satu digit pun berubah:
const obs = JSON.parse(line, (key, value, context) =>
typeof value === "number" ? JSON.rawJSON(context.source) : value);
obs.valueQuantity.value.rawJSON // "36.50" → simpan ke kolom NUMERIC
JSON.stringify(obs) // ..."value":36.50... → kirim kembali ke server FHIR
Semua hasil di atas saya jalankan di Node 24.21.0. context.source dan JSON.rawJSON berasal dari proposal TC39 "JSON.parse source text access", yang "is at stage 4 of the TC39 Process";
motivasinya persis masalah ini: "Transformation between ECMAScript values and JSON text is lossy." [5]
Untuk perhitungan, nilai boleh diubah ke Number: FHIR menulis "Implementations are not required to perform calculations with these numbers differently". Yang wajib dijaga adalah presisi nilai yang disimpan dan ditampilkan. [1]
Latihan 1 — Presisi
Latihan 2 — Ucapkan dengan suara keras
Pasang timer dan jawab dengan suara keras, seolah sedang interview. Jangan buka jawaban contoh sebelum selesai.
"Observation dari server FHIR berisi valueQuantity.value 36.50. Bagaimana Anda menjaga presisinya dari parse, simpan ke database, sampai dikirim kembali?" (60 detik)
Bandingkan dengan jawaban contoh dan checklist-nya
"Di FHIR presisi decimal adalah bagian dari data: 0.010 berbeda dengan 0.01, dan implementasi SHALL mempertahankannya. Masalahnya, JSON.parse mengubah angka JSON jadi Number JavaScript,
jadi 36.50 menjadi 36.5 dan 2.00 menjadi 2. Di Node 24 saya pakai reviver dengan context.source untuk mengambil teks asli angkanya, lalu menyimpannya sebagai string atau kolom NUMERIC,
bukan float. Saat mengirim kembali, decimal FHIR harus JSON number, jadi saya tidak mengirim string "36.50"; saya bungkus dengan JSON.rawJSON supaya JSON.stringify menulis 36.50 apa adanya.
Untuk perhitungan, FHIR tidak mewajibkan aritmetika khusus; yang wajib dijaga adalah nilai yang disimpan dan ditampilkan."
Checklist: apakah Anda menyebut…
- presisi decimal FHIR bermakna (0.010 ≠ 0.01) dan SHALL dipertahankan?
JSON.parsemengubah ke Number: 36.50 jadi 36.5, 2.00 jadi 2?- reviver
context.source(Node 24) untuk mengambil teks asli angka? - simpan sebagai string atau
NUMERIC, bukanfloat? - kirim kembali sebagai JSON number dengan
JSON.rawJSON, bukan JSON string? - perhitungan boleh memakai Number; yang wajib utuh adalah nilai tersimpan/tampil?
Lulus jika minimal 4 dari 6.
Modul Bulk Data selesai. Semua topiknya muncul lagi, diacak, di Review 2 setelah modul SMART.
_outputFormat, _since, _type), "Bulk Data Status Request", dan "Response - Complete Status" sampai contoh manifest.
Kalau masih ada waktu, panduan Node Backpressuring in Streams.
Istilah: GLOSSARY.md (bagian "Bulk Data dan JSON skala besar") · Indeks: semua lesson FHIR
Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "apa beda Patient/$export dan Group/[id]/$export untuk use case analitik?", "bagaimana menulis hasil export ke S3 dengan backpressure yang benar?", atau "kapan lebih baik memakai worker thread daripada streaming?"
Sumber: [1] FHIR R4: Data Types (decimal) · [2] FHIR R4: JSON Representation of Resources · [3] MDN: JSON.parse() · [4] MDN: JSON.rawJSON() · [5] TC39: JSON.parse source text access