JSON ออกจาก Infinity และ NaN; สถานะ JSON ใน ECMAScript?


180

มีความคิดใดบ้างที่ทำให้ JSON ออกจาก NaN และ +/- Infinity มันทำให้จาวาสคริปต์อยู่ในสถานการณ์ที่แปลกประหลาดซึ่งวัตถุที่จะเป็นแบบอนุกรม, มิใช่, ถ้ามันมีค่า NaN หรือ +/- อินฟินิตี้

ดูเหมือนว่าสิ่งนี้ถูกนำแสดงโดย: ดูRFC4627และECMA-262 (ส่วน 24.5.2, JSON.stringify, NOTE 4, หน้า 683 ของไฟล์ PDF ECMA-262 ที่แก้ไขล่าสุด):

ตัวเลขจํากัด stringified ToString(number)เช่นถ้าโดยการเรียก น่านnullและอินฟินิตี้โดยไม่คำนึงถึงสัญญาณจะแสดงเป็นสตริง


ฉันไม่พบใบเสนอราคานั้นในเอกสารใดเอกสารหนึ่ง
wingedsubmariner

1
แก้ไขมันดูเหมือนว่ามีการอ้างอิงเก่า / เก่าแก้ไขอย่างใด
Jason S

คำตอบ:


90

InfinityและNaNไม่ใช่คำหลักหรืออะไรเป็นพิเศษ แต่เป็นเพียงคุณสมบัติบนออบเจ็กต์ทั่วโลก (ตามที่เป็นundefined) และสามารถเปลี่ยนแปลงได้ มันเป็นเหตุผลที่ทำให้ JSON ไม่ได้รวมไว้ในสเปค - ในสาระสำคัญสตริง JSON ใด ๆ ที่ความจริงควรจะมีผลเดียวกันใน ECMAScript ถ้าคุณทำหรือeval(jsonString)JSON.parse(jsonString)

หากได้รับอนุญาตจากนั้นมีคนสามารถฉีดรหัสคล้ายกับ

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

ลงในฟอรัม (หรืออะไรก็ตาม) จากนั้นการใช้งาน json บนไซต์นั้นอาจถูกบุกรุก


29
ถ้าคุณประเมิน 1/0 คุณจะได้อินฟินิตี้ถ้าคุณประเมิน -1/0 คุณจะได้รับ - อินฟินิตี้ถ้าคุณประเมิน 0/0 คุณจะได้ NaN
46499 Jason S

9
แต่คำศัพท์NaNและInfinityเป็นชื่อคุณสมบัติดังนั้นในขณะที่ String (1/0) จะสร้างสตริง"Infinity"ที่เป็นเพียงการแสดงสตริงของอินฟินิตี้ค่า มันเป็นไปไม่ได้ที่จะแสดงอย่างใดอย่างหนึ่งNaNหรือInfinityเป็นค่าที่แท้จริงคือ ES - คุณต้องใช้นิพจน์ (เช่น 1/0, 0/0 เป็นต้น) หรือการค้นหาคุณสมบัติ (อ้างอิงถึงInfinityหรือNaN) เนื่องจากต้องการการเรียกใช้รหัสจึงไม่สามารถรวมใน JSON ได้
olliej

16
ถึงจุดของคุณเกี่ยวกับความปลอดภัย / การรักษาความปลอดภัยตัวแยกวิเคราะห์ JSON ที่เหมาะสมทั้งหมดจะต้องทำเมื่อไปที่การแปลง NaN คือการให้ค่า 0/0 (แทนที่จะประเมินสัญลักษณ์ NaN) ซึ่งจะส่งกลับ "จริง" NaN โดยไม่คำนึงถึงสิ่งที่ สัญลักษณ์ NaN ถูกนิยามใหม่เป็น
46499 Jason S

33
@olliej: คุณยืนยันว่า NaN ไม่ใช่ตัวอักษรฉันไม่รู้จาวาสคริปต์เพียงพอที่จะตัดสินความหมายของจาวาสคริปต์ แต่สำหรับรูปแบบไฟล์ที่เก็บหมายเลขทศนิยมความแม่นยำสองเท่าควรมีวิธีการกำหนด IEEE ลอยเช่นโดยตัวอักษร NaN / Infinity / NegInfinity เหล่านี้คือสถานะของ 64 บิตเป็นสองเท่าและควรเป็นตัวแทน มีคนที่พึ่งพาพวกเขา (ด้วยเหตุผล) พวกเขาอาจจะลืมเพราะ JSON / Javascript มาในการพัฒนาเว็บแทนการคำนวณทางวิทยาศาสตร์
wirrbel

35
มันเป็น 100% อย่างไม่ถูกต้องสำหรับ JSON ที่จะละเว้นโดยพลการที่สมบูรณ์และถูกต้องระบุจำนวนจุดลอยตัวมาตรฐานของ NaN, Infinity และ -Infinity โดยพื้นฐานแล้ว JSON ตัดสินใจที่จะสนับสนุนชุดย่อยของค่าลอยตัว IEEE ตามอำเภอใจโดยไม่ตั้งใจละเว้นค่าเฉพาะสามค่าเนื่องจากมันยากหรือบางอย่าง ไม่ได้ความสามารถของ Eval ไม่ได้เป็นข้อแก้ตัวเลยเพราะตัวเลขเหล่านี้อาจถูกเข้ารหัสเป็นตัวอักษร 1/0, -1/0 และ 0/0 ตัวเลขเหล่านี้จะเป็นตัวเลขที่ถูกต้องต่อท้ายด้วย "/ 0" ซึ่งไม่เพียง แต่ง่ายต่อการตรวจจับ แต่สามารถประเมินเป็น ES ได้ในเวลาเดียวกัน ไม่มีข้อแก้ตัว
Triynko

56

ในคำถามเดิม: ฉันเห็นด้วยกับผู้ใช้ "cbare" ในที่นี้เป็นการละเว้นโชคร้ายใน JSON IEEE754 กำหนดว่าเป็นค่าพิเศษสามค่าของเลขทศนิยม ดังนั้น JSON จึงไม่สามารถแสดงตัวเลขทศนิยมของ IEEE754 ได้อย่างสมบูรณ์ ในความเป็นจริงยิ่งแย่ลงเนื่องจาก JSON ตามที่กำหนดไว้ใน ECMA262 5.1 ไม่ได้กำหนดว่าตัวเลขจะขึ้นอยู่กับ IEEE754 เนื่องจากโฟลว์การออกแบบที่อธิบายไว้สำหรับฟังก์ชัน stringify () ใน ECMA262 ไม่ได้กล่าวถึงค่า IEEE พิเศษสามค่าเราสามารถสงสัยได้ว่าเจตนานั้นเป็นจริงในการรองรับหมายเลขจุดลอยตัว IEEE754

ในฐานะที่เป็นจุดข้อมูลอื่น ๆ ไม่เกี่ยวข้องกับคำถาม: XML datatypes xs: float และ xs: double do ระบุว่าขึ้นอยู่กับตัวเลขทศนิยม IEEE754 และสนับสนุนการแสดงค่าพิเศษทั้งสาม (ดู W3C XSD 1.0 ตอนที่ 2 , ประเภทข้อมูล)


5
ฉันยอมรับว่านี่เป็นเรื่องโชคร้าย แต่อาจเป็นสิ่งที่ดีที่หมายเลข JSON ไม่ได้ระบุรูปแบบจุดลอยตัวที่แน่นอน แม้แต่ IEEE754 ยังระบุหลายรูปแบบ - ขนาดแตกต่างกันและความแตกต่างระหว่างเลขทศนิยมและเลขยกกำลังเลขฐานสอง JSON นั้นเหมาะสมอย่างยิ่งกับทศนิยมดังนั้นมันน่าเสียดายถ้ามาตรฐานบางตัวตรึงมันให้เป็นไบนารี
Adrian Ratnapala

5
@AdrianRatnapala +1 แน่นอน: ตัวเลข JSON อาจมีความแม่นยำไม่ จำกัด ดังนั้นจะดีกว่าข้อกำหนด IEEE มากเนื่องจากไม่มีขนาด จำกัด ไม่มีขีด จำกัด ความแม่นยำและไม่มีผลการปัดเศษ (ถ้าอนุกรมสามารถจัดการได้)
Arnaud Bouchez

2
@ArnaudBouchez ที่กล่าวว่า JSON ยังควรสนับสนุนสตริงที่เป็นตัวแทนของ NaN และ + -Infinity แม้ว่า JSON ไม่ควรถูกตรึงอยู่กับรูปแบบ IEEE ใด ๆ ก็ตามผู้ที่กำหนดรูปแบบตัวเลขควรอย่างน้อยควรดูที่หน้าวิกิพีเดีย IEEE754 และหยุดคิดสักครู่
Adrian Ratnapala


นี่ไม่ใช่โชคร้าย ดูคำตอบโดย @CervEd มันไม่ได้เชื่อมโยงกับ IEE754 ซึ่งเป็นสิ่งที่ดี (แม้ว่าภาษาการเขียนโปรแกรมส่วนใหญ่จะใช้ IEEE754 และต้องมีการประมวลผลเพิ่มเติมในกรณีของ NaN และอื่น ๆ )
Ludovic Kuty

16

คุณสามารถปรับรูปแบบวัตถุ null และใน JSON ของคุณเป็นตัวแทนของค่าเช่น

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

จากนั้นเมื่อตรวจสอบคุณสามารถตรวจสอบประเภท

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

ฉันรู้ว่าใน Java คุณสามารถแทนที่วิธีการทำให้เป็นอันดับเพื่อนำสิ่งนี้ไปใช้ ไม่แน่ใจว่าการจัดลำดับของคุณมาจากที่ใดดังนั้นฉันจึงไม่สามารถให้รายละเอียดเกี่ยวกับวิธีการนำไปใช้ในวิธีการทำให้เป็นอนุกรม


1
อืม ... นั่นเป็นคำตอบสำหรับวิธีแก้ปัญหา ฉันไม่ได้ขอวิธีแก้ปัญหาจริงๆ แต่ให้เหตุผลว่าทำไมค่าเหล่านี้จึงถูกยกเว้น แต่ +1 อยู่ดี
46499 Jason S

2
@Zoidberg: undefinedไม่ได้คำหลักที่มันเป็นสถานที่ให้บริการระดับโลกบนวัตถุที่
olliej

2
@Zoidberg: ไม่ได้กำหนดเป็นคุณสมบัติในวัตถุทั่วโลก - มันไม่ใช่คำหลักดังนั้นจึง"undefined" in thisส่งกลับค่าจริงในขอบเขตทั่วโลก นอกจากนี้ยังหมายความว่าคุณสามารถทำundefined = 42และif (myVar == undefined)กลายเป็น myVar == 42(หลัก) สิ่งนี้กลับไปสู่ยุคแรก ๆ ของ ecmascript nee javascript ซึ่งundefinedไม่มีอยู่ตามค่าเริ่มต้นดังนั้นผู้คนจึงทำได้var undefinedในขอบเขตทั่วโลก ดังนั้นจึงundefinedไม่สามารถสร้างคำหลักได้โดยไม่ทำให้ไซต์ที่มีอยู่เสียหายดังนั้นเราจึงถึงเวลาที่จะไม่ได้กำหนดเป็นสถานที่ให้บริการตามปกติ
olliej

2
@olliej: ฉันไม่รู้ว่าทำไมคุณคิดว่าไม่ได้กำหนดเป็นทรัพย์สินในวัตถุทั่วโลก โดยค่าเริ่มต้นการค้นหาไม่ได้กำหนดเป็นค่าในตัวของไม่ได้กำหนด หากคุณแทนที่ด้วย "undefined = 42" ดังนั้นเมื่อคุณเข้าถึงไม่ได้กำหนดเป็นการค้นหาตัวแปรคุณจะได้รับค่าแทนที่ แต่ลองทำ "zz = undefined; undefined = 42; x = {}; 'undefined old =' + (xa === zz) + ', ไม่ได้กำหนดใหม่ =' + (xa === ไม่ได้กำหนด) ' คุณไม่สามารถกำหนดค่าภายในของ null, ไม่ได้กำหนด, NaN หรือ Infinity อีกครั้งแม้ว่าคุณจะสามารถแทนที่การค้นหาสัญลักษณ์ของพวกเขาได้
46499 Jason S

2
@Jason undefinedเป็นสถานที่ให้บริการทั่วโลกเพราะมีการระบุเช่นนี้ ดู 15.1.1.3 จาก ECMAScript-262 ed 3
kangax

11

สตริง "Infinity", "-Infinity" และ "NaN" ทั้งหมดเชื่อมโยงกับค่าที่คาดหวังใน JS ดังนั้นฉันจะเถียงวิธีที่ถูกต้องในการแสดงค่าเหล่านี้ใน JSON เป็นสตริง

> +"Infinity"
Infinity

> +"-Infinity"
-Infinity

> +"NaN"
NaN

มันเป็นเพียงความอัปยศ JSON.stringify ไม่ได้ทำตามค่าเริ่มต้น แต่มีวิธี:

> JSON.stringify({ x: Infinity }, function (k,v) { return v === Infinity ? "Infinity" : v; })
"{"x":"Infinity"}"

1
0/0 เป็นต้นไม่ใช่ JSON ที่ถูกต้อง คุณต้องทำงานภายในขอบเขตของมาตรฐานและสตริงทำงานได้ดี
teh_senaus

ในทางตรงกันข้ามฉันคิดว่านี่เป็นวิธีแก้ปัญหาในทางปฏิบัติเพียงอย่างเดียว แต่ฉันจะทำหน้าที่ส่งคืน NaN หากค่าอินพุตคือ "NaN" ฯลฯ วิธีที่คุณทำการแปลงมีแนวโน้มที่จะฉีดรหัส
Marco Sulla

3
ค่า JSON ไม่สามารถเป็นนิพจน์ทางคณิตศาสตร์ ... เป้าหมายของการสร้างมาตรฐานแยกต่างหากจากไวยากรณ์ตัวอักษรภาษาคือการทำให้ JSON deeserializable โดยไม่ต้องดำเนินการใด ๆ มันเป็นรหัส ไม่แน่ใจว่าทำไมเราไม่สามารถมีNaNและInfinityเพิ่มเป็นค่าคำหลักเช่นtrueและfalseแม้ว่า
Mark Reed

ที่จะทำให้มันชัดเจนมากขึ้นเราสามารถใช้Number("Infinity"), Number("-Infinity")และNumber("NaN")
HKTonyLee

นี่เป็นงานที่เหมือนเวทมนตร์ JSON.parse("{ \"value\" : -1e99999 }")กลับมาอย่างง่ายดาย{ value:-Infinity }ใน javascript เฉพาะมันไม่เข้ากันกับประเภทตัวเลขที่กำหนดเองซึ่งอาจใหญ่กว่านั้น
Thaina

7

หากคุณมีการเข้าถึงรหัสซีเรียลไลซ์เซชันคุณอาจแทน Infinity เป็น 1.0e + 1024 เลขชี้กำลังมีขนาดใหญ่เกินไปที่จะเป็นตัวแทนในสองครั้งและเมื่อ deserialized นี้จะแสดงเป็นไม่มีที่สิ้นสุด ทำงานบน webkit ไม่แน่ใจเกี่ยวกับ json parsers อื่น ๆ !


4
IEEE754 รองรับตัวเลขจุดลอยตัว 128 บิตดังนั้น 1.0e5000 จึงดีกว่า
Ton Plomp

2
ตัน: 128 บิตถูกเพิ่มในภายหลัง ถ้าพวกเขาตัดสินใจที่จะเพิ่ม 256 บิต จากนั้นคุณจะต้องเพิ่มศูนย์เพิ่มเติมและรหัสที่มีอยู่จะทำงานแตกต่างกัน Infinityจะเป็นInfinityดังนั้นทำไมไม่สนับสนุนสิ่งนั้น
บินแกะ

1
ความคิดที่ฉลาด! ฉันเพิ่งจะเปลี่ยนเป็นรูปแบบที่แตกต่างกันหรือเพิ่มรหัสการแก้ไขปัญหายุ่งยากในการแยกวิเคราะห์ของฉัน ไม่เหมาะสำหรับทุกกรณี แต่ในกรณีของฉันที่อินฟินิตี้ทำหน้าที่เป็นเพียงแค่ขอบกรณีที่ปรับให้เหมาะกับลำดับการบรรจบกันมันสมบูรณ์แบบและแม้ว่าความแม่นยำที่ยิ่งใหญ่กว่าจะแนะนำก็จะยังคงถูกต้องเป็นส่วนใหญ่ ขอบคุณ!
หรือ Sharir

3
1, -1, และ 0 ..... ตัวเลขที่ถูกต้อง / แยกได้อย่างสมบูรณ์กลายเป็นสามค่าพิเศษเมื่อคุณเพิ่ม/0ไปยังจุดสิ้นสุดของพวกเขา สามารถแยกวิเคราะห์ได้ง่ายมองเห็นได้ทันทีและประเมินผลได้ เป็นอภัยไม่ได้ที่พวกเขายังไม่ได้เพิ่มลงในมาตรฐาน: {"Not A Number":0/0,"Infinity":1/0,"Negative Infinity":-1/0} << ทำไมไม่ alert(eval("\"Not A Number\"") //works alert(eval("1/0")) //also works, prints 'Infinity'. ไม่มีข้อแก้ตัว
Triynko


1

IEEE Std ปัจจุบัน 754-2008 มีคำจำกัดความสำหรับการเป็นตัวแทนจุดลอยตัว 64 บิตที่แตกต่างกันสองแบบ: ทศนิยมชนิดทศนิยม 64 บิตและทศนิยมชนิดไบนารี 64 บิต

หลังจากการปัดเศษสตริง.99999990000000006จะเหมือนกับ.9999999ใน IEEE 64 บิต 64 บิต แต่ไม่เหมือนกับ.9999999ใน IE64 64 บิตทศนิยมแทน ในทศนิยมทศนิยม IEEE แบบ 64 บิต.99999990000000006จะปัดเศษเป็นค่า.9999999000000001ซึ่งไม่เหมือนกับ.9999999ค่าทศนิยม

เนื่องจาก JSON ใช้ค่าตัวเลขเป็นสตริงตัวเลขของทศนิยมจึงไม่มีวิธีใดที่ระบบที่รองรับทั้ง IEEE ไบนารีและการแทนค่าทศนิยมทศนิยม (เช่น IBM Power) เพื่อกำหนดว่าค่าทศนิยมใด ๆ ของตัวเลข IEEE ที่เป็นไปได้สองค่าคือ ตั้งใจว่า


สิ่งนี้เกี่ยวข้องกับคำถามได้อย่างไร (ซึ่งเกี่ยวกับ Infinity และ NaN)
Bryan

1

ความเป็นไปได้ในการแก้ไขสำหรับกรณีเช่น {"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;
   });

แนวคิดทั่วไปคือการแทนที่การเกิดขึ้นของค่าที่ไม่ถูกต้องด้วยสตริงที่เราจะรับรู้เมื่อแยกและแทนที่ด้วยการเป็นตัวแทน JavaScript ที่เหมาะสม


ฉันไม่รู้ว่าเพราะเหตุใดโซลูชันนี้จึงมี downvote เพราะตรงไปตรงมาหากคุณเผชิญกับสถานการณ์ที่สตริง JSON ของคุณมีค่า Infinity หรือ IsNaN ค่าจะล้มเหลวเมื่อคุณพยายามแยกวิเคราะห์ การใช้เทคนิคนี้คุณจะแทนที่ IsNaN หรือ Infinity ด้วยสิ่งอื่น (เพื่อแยกพวกมันออกจากสตริงที่ถูกต้องซึ่งอาจมีเงื่อนไขเหล่านั้น) และใช้ JSON.parse (สตริงการโทรกลับ) เพื่อส่งคืนค่า JavaScript ที่ถูกต้อง ฉันใช้สิ่งนี้ในรหัสการผลิตและไม่เคยมีปัญหาใด ๆ
SHamel

สิ่งนี้จะไม่ทำให้อินฟินิตี้ภายในสายขาดหรือไม่ สำหรับผู้ใช้หลาย ๆ คนมันอาจจะปลอดภัยที่จะคิดว่ามันไม่ใช่ปัญหา แต่วิธีการแก้ปัญหานั้นยังไม่สมบูรณ์
olejorgenb

1

เหตุผลระบุไว้ในหน้า 2 ในStandard ECMA-404 ไวยากรณ์การแลกเปลี่ยนข้อมูล JSON รุ่นที่ 1

JSON ไม่เชื่อเรื่องตัวเลข ในภาษาการเขียนโปรแกรมใด ๆ อาจมีหลากหลายประเภทของความสามารถและความหลากหลายที่หลากหลายคงที่หรือลอยตัวไบนารีหรือทศนิยม ที่สามารถทำการแลกเปลี่ยนระหว่างภาษาการเขียนโปรแกรมที่แตกต่างกันได้ยาก JSON จะเสนอเฉพาะตัวเลขที่มนุษย์ใช้แทน: ลำดับของตัวเลข ภาษาการเขียนโปรแกรมทั้งหมดรู้วิธีที่จะเข้าใจถึงลำดับเลขแม้ว่าพวกเขาจะไม่เห็นด้วยกับการเป็นตัวแทนภายใน นั่นก็เพียงพอแล้วที่จะยอมให้มีการแลกเปลี่ยน

เหตุผลไม่ได้เป็นจำนวนมากได้อ้างสิทธิ์เนื่องจากการเป็นตัวแทนของNaNและInfinityสคริปต์ ECMA Simplicity เป็นหลักการออกแบบหลักของ JSON

เนื่องจากมันง่ายมากจึงไม่คาดว่าไวยากรณ์ JSON จะเปลี่ยนไป สิ่งนี้ทำให้ JSON เป็นรากฐานที่มั่นคงมาก


-3

ถ้าเหมือนฉันคุณไม่สามารถควบคุมรหัสซีเรียลไลซ์เซชั่นได้คุณสามารถจัดการกับค่า NaN ได้โดยแทนที่พวกเขาด้วยค่า Null หรือค่าอื่น ๆ เป็นบิตของแฮ็คดังนี้:

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

ในสาระสำคัญ. ความผิดพลาดจะได้รับการเรียกเมื่อ json parser เดิมตรวจพบโทเค็นที่ไม่ถูกต้อง จากนั้นสตริงแทนที่จะถูกใช้เพื่อแทนที่โทเค็นที่ไม่ถูกต้อง ในกรณีของฉันมันเป็นข้อยกเว้นสำหรับ serialiser เพื่อส่งกลับค่า NaN ดังนั้นวิธีนี้เป็นวิธีที่ดีที่สุด หากผลลัพธ์มีโทเค็นที่ไม่ถูกต้องคุณจะดีกว่าที่จะไม่ใช้ $ .get แต่เพื่อดึงผลลัพธ์ JSON ด้วยตนเองและรันการแทนที่สตริงเสมอ


21
ฉลาด แต่ไม่เข้าใจผิดทั้งหมด ทดลองใช้กับ{ "tune": "NaNaNaNaNaNaNaNa BATMAN", "score": NaN }
JJJ

1
และคุณต้องใช้ jQuery ฉันไม่มี $ .get ()
Jason S
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.