Alat pengembang · JSON pemformat & validator
ID bilangan bulat besar di JSON: mengapa pemformat JavaScript dapat membulatkannya
· Mengapa itu penting
json alur kerja pengembang validasi
JSON mengizinkan bilangan bulat dengan ukuran berapa pun, tetapi JavaScript mewakili angka sebagai 64-bit float, jadi apa pun di atas 2^53 dapat berubah saat diurai dan diserialisasi ulang. Postingan ini menjelaskan batasannya, cara mengenali kerusakan, dan cara melindungi ID.
ID yang diubah satu
ID yang diubah oleh seseorang sering kali dianggap sebagai JSON yang benar-benar valid. Masukkan `{"orderId":9007199254740993}` ke dalam JavaScript dan `JSON.parse` mengembalikan Nomor yang nilai yang ditampilkan adalah `9007199254740992`. Penguraian berhasil karena token mengikuti JSON tata bahasa angka; kerusakan terjadi saat mengubah angka desimal tersebut menjadi representasi numerik JavaScript. Pemformat yang membuat serialisasi nilai yang diuraikan dengan setia menulis Angka yang dibulatkan, bukan token persis yang muncul di sumbernya.
Perbedaannya langsung terlihat ketika angka yang sama dikutip. `JSON.parse("{"orderId":"9007199254740993"}")` mengembalikan string `9007199254740993`, mempertahankan setiap karakter, dan `JSON.stringify` memancarkan digit tersebut tanpa perubahan di dalam tanda kutip. Inilah sebabnya mengapa validasi sintaksis saja tidak dapat melindungi pengidentifikasi numerik. Bandingkan input dan output setiap kali bilangan bulat panjang muncul, dan perlakukan pengidentifikasi sebagai string pada batas produksi ketika aritmatika bukan bagian dari maknanya.
Apa yang RFC 8259 katakan tentang angka
RFC 8259 mendefinisikan ejaan angka JSON tetapi tidak memberikan tipe numerik presisi arbitrer pada setiap implementasi. Tata bahasanya mengizinkan tanda minus opsional, bagian bilangan bulat, dan bagian pecahan dan eksponen opsional. Ini tidak termasuk kemudahan seperti notasi heksadesimal, `NaN` dan `Infinity`. Akibatnya, `9007199254740993` valid secara sintaksis meskipun konsumen umum JavaScript tidak dapat merepresentasikan bilangan bulat tersebut persis sebagai Angka.
Panduan interoperabilitas spesifikasi adalah peringatan praktis: perangkat lunak biasanya menggunakan angka IEEE 754 biner64, dan bilangan bulat dalam rentang dari `2^53 + 1` negatif hingga `2^53 - 1` positif dapat dioperasikan dalam artian kesepakatan yang tepat. Validator dapat menerima token yang lebih besar dengan benar sementara parser kemudian membulatkannya.
Dari mana 2^53 berasal
Batas `2^53` berasal dari presisi yang tersedia dalam signifikansi biner64. JavaScript menampilkan bilangan bulat tertinggi yang dapat direpresentasikan secara berurutan sebagai `Number.MAX_SAFE_INTEGER`, yaitu `9007199254740991`. Pada dan di bawah besaran tersebut, bilangan bulat yang berdekatan dapat direpresentasikan dengan jelas. Di atasnya, jarak antar nilai yang dapat direpresentasikan bertambah, sehingga beberapa bilangan bulat desimal yang berdekatan dipetakan ke Angka yang sama. Runtime tidak memotong string; itu adalah memilih nilai terdekat yang tersedia dalam format biner terbatas itu.
Pemeriksaan konsol yang terungkap adalah `Number.isSafeInteger(9007199254740993)`, yang salah, meskipun literal sumber telah dibulatkan sebelum fungsi menerimanya. Lainnya adalah `9007199254740992 === 9007199254740993`, yang bernilai benar di JavaScript. Contoh-contoh ini berkaitan dengan identitas bilangan bulat yang sebenarnya, bukan apakah setiap bilangan yang lebih besar menjadi tidak dapat digunakan.
Bagaimana parse-and-reserialise kehilangan digit
Pemformatan parsing dan serialisasi ulang memiliki tiga tahap: membaca karakter numerik, membuat nilai dalam memori, lalu menghasilkan karakter baru dari nilai tersebut. Detail leksikal menghilang di tahap tengah. Dengan `{"ticket":9223372036854775807}`, `JSON.parse` membuat Nomor JavaScript terdekat yang tersedia; `JSON.stringify` lalu memancarkan `9223372036854776000`. Serializer tidak secara independen merusak token yang disimpan. Pada waktu serialisasi, urutan digit asli tidak lagi ada dalam objek yang diurai.
Implementasi repositori ToolAcre menggunakan `JSON.parse` dan `JSON.stringify`, jadi batasan ini berlaku untuk keluaran yang diformat. Pemindai sintaksisnya berjalan untuk memberikan alasan dan lokasi yang stabil setelah penguraian gagal; itu tidak menggantikan angka JavaScript dengan representasi presisi sewenang-wenang. Oleh karena itu, hasil validasi yang berhasil akan menentukan tata bahasa, sedangkan perbedaan format dapat menunjukkan hilangnya presisi.
Contoh praktis: membandingkan input dan output
Bandingkan `{"numeric":9007199254740993,"text":"9007199254740993"}` sebelum dan sesudah perjalanan pulang pergi JavaScript. Menjalankan `JSON.stringify(JSON.parse(source), null, 2)` menghasilkan objek yang diformat dengan anggota `numeric` adalah `9007199254740992`, sedangkan `text` tetap `"9007199254740993"`. Kedua anggota tersebut valid pada masukannya, dan keduanya tetap valid pada keluarannya. Hanya representasi yang dikutip yang mempertahankan pengidentifikasi karena didekodekan sebagai data karakter, bukan Nomor.
Tinjauan yang bermanfaat tidak hanya menanyakan apakah formatter menunjukkan warna hijau. Cari sumber untuk jaringan digit yang tidak terputus, bandingkan nilai apa pun yang lebih panjang dari rentang aman, dan tentukan apakah setiap bidang mewakili kuantitas atau label buram. Jika produsen mengontrol kontrak, ubah label menjadi string di sana dan dokumentasikan pilihan tersebut untuk konsumen.
Melindungi ID di sumbernya
Lindungi ID di sumbernya dengan mendefinisikannya sebagai string dalam skema dan membuat serialisasinya sebagai string sebelum klien JavaScript mana pun menerima payload. ID hanya boleh berisi angka dan tetap memiliki arti nonnumerik: penambahan, pembulatan, dan pengurutan berdasarkan besarnya bukanlah operasi yang sah pada kunci akun. Sebuah string juga mempertahankan angka nol di depan, yang akan dibuang oleh representasi numerik bahkan ketika besarnya berada dalam kisaran aman.
Jangan menyimpulkan keamanan lintas bahasa dari fakta bahwa runtime lain dapat menampung bilangan bulat yang lebih besar. Parser dan jenis target bervariasi, dan perantara yang ditulis dalam JavaScript dapat membulatkan nilai sebelum layanan selanjutnya melihatnya. Beberapa parser khusus menyimpan token angka atau membuat bilangan bulat besar, namun setiap peserta harus berbagi kontrak tersebut.
Hal ini tidak tercakup dalam hal ini
Yang tidak tercakup di sini adalah desain aritmatika desimal yang lebih luas. Nilai seperti `0.1` memiliki perilaku titik mengambang binernya sendiri, dan uang mungkin memerlukan bilangan bulat berskala atau tipe desimal sesuai dengan kontrak aplikasi. Mengutip setiap angka juga tidak secara otomatis memperbaiki skema. Hitungan, koordinat, dan pengukuran sering kali bersifat numerik. Keputusannya bergantung pada apakah ejaan desimal yang tepat atau identitas bilangan bulat yang tepat harus bertahan di setiap konsumen di jalur data.
Diskusi ini juga tidak mengklaim bahwa JSON itu sendiri yang membulatkan token atau bahwa semua parser berperilaku seperti JavaScript. Bukti repositori yang konkrit lebih sempit: pemformat ini memanggil `JSON.parse` dan `JSON.stringify`, jadi JavaScript Semantik angka mengatur nilai yang tidak diberi tanda kutip di sini. Pustaka JSON dengan presisi arbitrer dapat membuat pilihan berbeda, namun harus menentukan cara nilai diekspos dan diserialkan.
Kesimpulan: angka di atas 2^53 termasuk dalam string
Kesimpulannya spesifik: pengidentifikasi bilangan bulat di luar rentang aman JavaScript termasuk dalam string ketika harus melewati JavaScript tanpa berubah. `9007199254740993` sebagai angka JSON adalah sintaks yang valid tetapi menjadi `9007199254740992` setelah `JSON.parse`; `"9007199254740993"` tetap tepat. Kutipan tersebut bukanlah hiasan. Mereka memilih representasi yang mempertahankan angka sebagai data dan mencegah konsumen memperlakukan label buram sebagai perkiraan kuantitas.
Sebelum mengganti dokumen dengan keluaran formatter, bandingkan angka panjang dengan aslinya dan selidiki setiap digit yang diubah. Perbaiki produser dan skema jika memungkinkan sehingga semua klien hilir menerima formulir aman secara konsisten. ToolAcre dapat mengekspos konsekuensinya karena outputnya mencerminkan nilai JavaScript yang diurai, namun tidak dapat merekonstruksi digit yang sudah hilang selama penguraian.