JSON mengabaikan Infinity dan NaN; Status JSON dalam ECMAScript?

180

Tahu mengapa JSON mengabaikan NaN dan +/- Infinity? Ini menempatkan Javascript dalam situasi aneh di mana objek yang seharusnya serializable, tidak, jika mereka berisi nilai infinity NaN atau +/-.

Sepertinya ini telah dilemparkan ke batu: lihat RFC4627 dan ECMA-262 (bagian 24.5.2, JSON.stringify, CATATAN 4, halaman 683 dari ECMA-262 pdf pada edit terakhir):

Nomor-nomor yang terbatas dirangkai seolah-olah dengan menelepon ToString(number). NaN dan Infinity terlepas dari tanda diwakili sebagai String null.

Jason S
sumber
Saya tidak dapat menemukan kutipan itu di kedua dokumen.
wingedsubmariner
1
memperbaikinya, sepertinya ada basi referensi / basi edit entah bagaimana.
Jason S

Jawaban:

90

Infinitydan NaNbukan kata kunci atau sesuatu yang istimewa, itu hanya properti pada objek global (apa adanya undefined) dan karenanya dapat diubah. Karena alasan itulah JSON tidak memasukkannya ke dalam spec - pada intinya string JSON yang sebenarnya harus memiliki hasil yang sama dalam EcmaScript jika Anda melakukannya eval(jsonString)atau JSON.parse(jsonString).

Jika diizinkan maka seseorang dapat menyuntikkan kode serupa

NaN={valueOf:function(){ do evil }};
Infinity={valueOf:function(){ do evil }};

ke dalam forum (atau apa pun) dan kemudian penggunaan json di situs itu dapat dikompromikan.

olliej
sumber
29
Jika Anda mengevaluasi 1/0 Anda mendapatkan Infinity, jika Anda mengevaluasi -1/0 Anda mendapatkan -Infinity, jika Anda mengevaluasi 0/0 Anda mendapatkan NaN.
Jason S
9
Tetapi istilah NaNdan Infinitynama properti, jadi sementara String (1/0) menghasilkan string "Infinity"yang hanya representasi string dari nilai tak terhingga. Tidak mungkin untuk mewakili NaNatau Infinitykarena nilai literalnya adalah ES - Anda harus menggunakan ekspresi (mis. 1/0, 0/0 dll) atau pencarian properti (merujuk ke Infinityatau NaN). Karena mereka memerlukan eksekusi kode, mereka tidak dapat dimasukkan dalam JSON.
olliej
16
Untuk poin Anda tentang keselamatan / keamanan, semua parser JSON yang layak harus dilakukan ketika akan mengkonversi NaN adalah untuk menghasilkan nilai 0/0 (daripada mengevaluasi simbol NaN) yang akan mengembalikan NaN "nyata" terlepas dari apa simbol NaN didefinisikan ulang sebagai.
Jason S
33
@olliej: Anda berpendapat bahwa NaN bukan literal, saya tidak tahu cukup Javascript untuk menilai semantik javascript. Tetapi untuk format file yang menyimpan angka floating point presisi ganda, harus ada cara untuk mendefinisikan IEEE float, yaitu dengan literal NaN / Infinity / NegInfinity. Ini adalah status dari bit 64 bit dan karenanya harus diwakili. Ada orang yang bergantung pada mereka (untuk alasan). Mereka mungkin dilupakan karena JSON / Javascript berasal dari pengembangan web daripada komputasi ilmiah.
wirrbel
35
Ini adalah 100%, benar-benar SALAH bagi JSON untuk secara sewenang-wenang menghilangkan status angka floating-point yang valid dan standar dari NaN, Infinity, dan -Infinity. Pada dasarnya, JSON memutuskan untuk mendukung subset arbitrer dari nilai float IEEE, mengabaikan tiga nilai spesifik karena mereka sulit atau sesuatu. Tidak. Kemampuan Eval bahkan bukan alasan, karena angka-angka seperti itu dapat dikodekan sebagai literal 1/0, -1/0, dan 0/0. Mereka akan menjadi angka yang valid ditambahkan dengan "/ 0", yang tidak hanya mudah dideteksi, tetapi sebenarnya dapat dievaluasi sebagai ES pada saat yang sama. Tidak ada alasan.
Triynko
56

Pada pertanyaan awal: Saya setuju dengan pengguna "cbare" karena ini merupakan kelalaian yang tidak menguntungkan di JSON. IEEE754 mendefinisikan ini sebagai tiga nilai khusus dari angka floating point. Jadi JSON tidak dapat sepenuhnya mewakili angka floating point IEEE754. Bahkan lebih buruk lagi, karena JSON sebagaimana didefinisikan dalam ECMA262 5.1 bahkan tidak menentukan apakah angka-angkanya didasarkan pada IEEE754. Karena aliran desain yang dideskripsikan untuk fungsi stringify () di ECMA262 tidak menyebutkan tiga nilai IEEE khusus, orang dapat menduga bahwa maksudnya sebenarnya adalah untuk mendukung angka-angka floating point IEEE754.

Sebagai satu titik data lainnya, tidak terkait dengan pertanyaan: XML tipe data xs: float dan xs: dobel menyatakan bahwa mereka didasarkan pada angka-angka floating point IEEE754, dan mereka mendukung representasi dari tiga nilai khusus ini (Lihat W3C XSD 1.0 Bagian 2 , Datatypes).

Andreas Maier
sumber
5
Saya setuju ini semua disayangkan. Tapi mungkin itu hal yang baik bahwa angka JSON tidak menentukan format floating point yang tepat. Bahkan IEEE754 menentukan banyak format - ukuran yang berbeda, dan perbedaan antara eksponen desimal dan biner. JSON sangat cocok untuk desimal, sehingga akan sangat disayangkan jika beberapa standar ingin menyematkannya ke biner.
Adrian Ratnapala
5
@AdrianRatnapala +1 Memang: angka JSON berpotensi memiliki presisi tak hingga, jadi jauh lebih baik daripada spesifikasi IEEE, karena mereka tidak memiliki batas ukuran, tidak ada batas presisi, dan tidak ada efek pembulatan (jika serializer dapat menanganinya).
Arnaud Bouchez
2
@ArnaudBouchez. Yang mengatakan, JSON harus tetap mendukung string yang mewakili NaN dan + -Infinity. Bahkan jika JSON tidak boleh disematkan ke format IEEE apa pun, orang yang mendefinisikan format angka setidaknya harus melihat halaman wikipedia IEEE754 dan berhenti sejenak untuk berpikir.
Adrian Ratnapala
Ini tidak disayangkan. Lihat jawabannya oleh @CervEd. Ini tidak terkait dengan IEE754 yang merupakan hal yang baik (bahkan jika sebagian besar bahasa pemrograman menggunakan IEEE754 dan karenanya memerlukan pemrosesan tambahan jika NaN, dll.).
Ludovic Kuty
16

Bisakah Anda mengadaptasi pola objek nol, dan di JSON Anda mewakili nilai-nilai seperti

"myNum" : {
   "isNaN" :false,
   "isInfinity" :true
}

Kemudian saat memeriksa, Anda dapat memeriksa jenisnya

if (typeof(myObj.myNum) == 'number') {/* do this */}
else if (myObj.myNum.isNaN) {/* do that*/}
else if (myObj.myNum.isInfinity) {/* Do another thing */}

Saya tahu di Java Anda bisa mengganti metode serialisasi untuk mengimplementasikan hal seperti itu. Tidak yakin dari mana asal serialisasi Anda, jadi saya tidak bisa memberikan detail tentang bagaimana menerapkannya dalam metode serialisasi.

Zoidberg
sumber
1
hmmm ... itu jawaban untuk solusi; Saya tidak benar-benar meminta solusi tetapi untuk alasan mengapa nilai-nilai ini tidak termasuk. Tapi +1 tetap.
Jason S
2
@ Zoidberg: undefinedbukan kata kunci, ini properti pada objek global
olliej
2
@Zoidberg: undefined adalah properti pada objek global - itu bukan kata kunci, jadi "undefined" in thismengembalikan true dalam lingkup global. Ini juga berarti Anda dapat melakukan undefined = 42dan if (myVar == undefined)menjadi (pada dasarnya) myVar == 42. Ini mengingatkan kembali pada hari-hari awal javascript ecmascript nee di mana undefinedtidak ada secara default, jadi orang hanya melakukannya var undefineddalam lingkup global. Konsekuensinya undefinedtidak dapat dibuat kata kunci tanpa merusak situs yang ada, jadi kami ditakdirkan untuk selamanya menjadi properti normal.
olliej
2
@olliej: Saya tidak tahu mengapa Anda berpikir undefined adalah properti pada objek global. Secara default pencarian undefined adalah nilai bawaan dari undefined. Jika Anda menimpanya dengan "undefined = 42" maka ketika Anda mengakses undefined sebagai variabel lookup, Anda mendapatkan nilai yang diganti. Tetapi coba lakukan "zz = undefined; undefined = 42; x = {}; 'undefined old =' + (xa === zz) + ', undefined baru =' + (xa === undefined)". Anda tidak pernah dapat mendefinisikan kembali nilai internal null, undefined, NaN, atau Infinity, bahkan jika Anda dapat mengganti pencarian simbol mereka.
Jason S
2
@Jason undefinedadalah properti global karena ditentukan seperti itu. Konsultasikan 15.1.1.3 dari ECMAScript-262 edisi ke-3.
kangax
11

String "Infinity", "-Infinity", dan "NaN" semuanya memaksa ke nilai yang diharapkan di JS. Jadi saya berpendapat cara yang tepat untuk mewakili nilai-nilai ini di JSON adalah sebagai string.

> +"Infinity"
Infinity

> +"-Infinity"
-Infinity

> +"NaN"
NaN

Sayang JSON.stringify tidak melakukan ini secara default. Tapi ada caranya:

> JSON.stringify({ x: Infinity }, function (k,v) { return v === Infinity ? "Infinity" : v; })
"{"x":"Infinity"}"
teh_senaus
sumber
1
0/0, dll, bukan JSON yang valid. Anda harus bekerja dalam batas-batas standar, dan string melakukan pekerjaan dengan baik.
teh_senaus
Sebaliknya, saya pikir ini adalah satu-satunya solusi praktis, tetapi saya akan melakukan fungsi yang mengembalikan NaN jika nilai inputnya adalah "NaN", dll. Cara Anda melakukan konversi cenderung terhadap injeksi kode.
Marco Sulla
3
Nilai JSON tidak dapat berupa ekspresi aritmatika ... tujuan membuat standar terpisah dari sintaks literal bahasa adalah untuk membuat JSON dapat dideabelkan tanpa menjalankannya sebagai kode. Namun, tidak yakin mengapa kami tidak dapat NaNdan Infinitymenambahkan sebagai nilai kata kunci suka truedan false.
Mark Reed
Untuk membuatnya lebih eksplisit, kita bisa menggunakan Number("Infinity"), Number("-Infinity")danNumber("NaN")
HKTonyLee
Ini bekerja seperti sihir. JSON.parse("{ \"value\" : -1e99999 }")mudah kembali { value:-Infinity }dalam javascript. Hanya saja tidak kompatibel dengan jenis nomor kustom yang bisa lebih besar dari itu
Thaina
7

Jika Anda memiliki akses ke kode serialisasi, Anda mungkin mewakili Infinity sebagai 1.0e + 1024. Eksponen terlalu besar untuk diwakili dalam dobel dan ketika deserialized ini diwakili sebagai Infinity. Bekerja di webkit, tidak yakin tentang parser json lainnya!

kuwerty
sumber
4
IEEE754 mendukung angka floating point 128 bit sehingga 1.0e5000 lebih baik
Ton Plomp
2
Ton: 128 bit ditambahkan kemudian. Bagaimana jika mereka memutuskan untuk menambah 256 bit? Maka Anda harus menambahkan lebih banyak nol, dan kode yang ada akan berperilaku berbeda. Infinityakan selalu Infinitybegitu, jadi mengapa tidak mendukungnya?
domba terbang
1
Ide pintar! Saya baru saja akan beralih ke format yang berbeda atau menambahkan kode solusi rumit ke parser saya. Tidak ideal untuk setiap kasus, tetapi dalam kasus saya, di mana infinity berfungsi hanya sebagai kasus tepi yang dioptimalkan untuk urutan konvergen, itu sempurna dan bahkan jika presisi yang lebih besar akan diperkenalkan itu masih sebagian besar masih benar. Terima kasih!
Atau Sharir
3
1, -1, dan 0 ..... angka yang benar-benar valid / dapat diuraikan, menjadi tiga nilai khusus ketika Anda cukup menambahkannya /0. Ini mudah diurai, langsung terlihat, dan bahkan dapat dievaluasi. Tidak dapat dimaafkan bahwa mereka belum menambahkannya ke standar: {"Not A Number":0/0,"Infinity":1/0,"Negative Infinity":-1/0} << Kenapa tidak? alert(eval("\"Not A Number\"") //works alert(eval("1/0")) //also works, prints 'Infinity'. Tidak ada alasan.
Triynko
1

IEEE Std 754-2008 saat ini mencakup definisi untuk dua representasi titik-mengambang 64-bit yang berbeda: tipe titik-floating-point 64-bit desimal dan tipe-titik floating-point biner 64-bit.

Setelah pembulatan string .99999990000000006adalah sama seperti .9999999dalam representasi 64-bit biner IEEE tetapi TIDAK sama dengan .9999999representasi 64-bit desimal IEEE. Dalam 64-bit IEEE desimal, floating-point .99999990000000006rounds ke nilai .9999999000000001yang tidak sama dengan nilai desimal .9999999.

Karena JSON hanya memperlakukan nilai numerik sebagai string numerik digit desimal, tidak ada cara untuk sistem yang mendukung representasi titik-biner IEEE dan floating-point desimal (seperti IBM Power) untuk menentukan yang mana dari dua kemungkinan nilai-nilai titik angka numerik IEEE yang mungkin. dimaksudkan.

Steven Hobbs
sumber
Apa hubungannya ini dengan pertanyaan? (yaitu tentang Infinity dan NaN)
Bryan
1

Potensi penyelesaian untuk kasus-kasus seperti {"key": Infinity}:

JSON.parse(theString.replace(/":(Infinity|-IsNaN)/g, '":"{{$1}}"'), function(k, v) {
   if (v === '{{Infinity}}') return Infinity;
   else if (v === '{{-Infinity}}') return -Infinity;
   else if (v === '{{NaN}}') return NaN;
   return v;
   });

Gagasan umum adalah mengganti kemunculan nilai yang tidak valid dengan string yang akan kami kenali saat mengurai dan menggantinya kembali dengan representasi JavaScript yang sesuai.

Shamel
sumber
Saya tidak tahu mengapa solusi ini mendapat downvote karena terus terang, jika Anda menghadapi situasi di mana string JSON Anda berisi nilai-nilai Infinity atau IsNaN itu akan gagal ketika Anda mencoba menguraikannya. Dengan menggunakan teknik ini, Anda pertama kali mengganti kemunculan IsNaN atau Infinity dengan sesuatu yang lain (untuk mengisolasi mereka dari string yang valid yang mungkin mengandung istilah-istilah itu), dan menggunakan JSON.parse (string, callback) untuk mengembalikan nilai-nilai JavaScript yang benar dan valid. Saya menggunakan ini dalam kode produksi dan tidak pernah mengalami masalah.
SHamel
Bukankah ini akan mengacaukan Infinity di dalam string? Bagi banyak pengguna, itu mungkin aman untuk menganggap itu bukan masalah, tetapi solusinya tidak sepenuhnya kuat.
olejorgenb
1

Alasannya dinyatakan pada halaman ii dalam Standard ECMA-404 JSON Data Interchange Syntax, 1st Edition

JSON agnostik tentang angka. Dalam bahasa pemrograman apa pun, bisa ada berbagai jenis nomor dari berbagai kapasitas dan komplemen, tetap atau mengambang, biner atau desimal. Itu dapat membuat pertukaran antar bahasa pemrograman yang berbeda menjadi sulit. JSON sebagai gantinya hanya menawarkan representasi angka yang digunakan manusia: urutan digit. Semua bahasa pemrograman tahu bagaimana memahami urutan digit meskipun mereka tidak setuju pada representasi internal. Itu cukup untuk memungkinkan pertukaran.

Alasannya bukan, seperti yang banyak diklaim, karena representasi NaNdan Infinityskrip ECMA. Kesederhanaan adalah prinsip desain inti JSON.

Karena sangat sederhana, tidak diharapkan bahwa tata bahasa JSON akan pernah berubah. Ini memberi JSON, sebagai notasi dasar, stabilitas luar biasa

CervEd
sumber
-3

Jika seperti saya Anda tidak memiliki kendali atas kode serialisasi, Anda dapat menangani nilai NaN dengan menggantinya dengan nol atau nilai lain sebagai sedikit peretasan sebagai berikut:

$.get("file.json", theCallback)
.fail(function(data) {
  theCallback(JSON.parse(data.responseText.replace(/NaN/g,'null'))); 
} );

Intinya, .fail akan dipanggil ketika parser json asli mendeteksi token yang tidak valid. Kemudian ganti string digunakan untuk mengganti token yang tidak valid. Dalam kasus saya ini adalah pengecualian untuk serialiser untuk mengembalikan nilai NaN sehingga metode ini adalah pendekatan terbaik. Jika hasil biasanya berisi token yang tidak valid Anda akan lebih baik untuk tidak menggunakan $ .get tetapi untuk secara manual mengambil hasil JSON dan selalu menjalankan penggantian string.

pengguna1478002
sumber
21
Pintar, tapi tidak sepenuhnya aman. Cobalah dengan{ "tune": "NaNaNaNaNaNaNaNa BATMAN", "score": NaN }
JJJ
1
dan Anda harus menggunakan jQuery. Saya tidak punya $ .get ().
Jason S