เครื่องมือสำหรับนักพัฒนา · JSON ฟอร์แมตเตอร์ & เครื่องมือตรวจสอบความถูกต้อง
รหัสจำนวนเต็มขนาดใหญ่ใน JSON: เหตุใดตัวจัดรูปแบบ JavaScript จึงปัดเศษได้
· เหตุใดจึงสำคัญ
json นักพัฒนาเวิร์กโฟลว์ การตรวจสอบ
JSON อนุญาตให้ใช้จำนวนเต็มทุกขนาด แต่ JavaScript แทนตัวเลขเป็น 64-บิตลอย ดังนั้นอะไรก็ตามที่อยู่เหนือ 2^53 สามารถเปลี่ยนแปลงได้เมื่อแยกวิเคราะห์และซีเรียลไลซ์ใหม่ โพสต์นี้จะอธิบายขีดจำกัด วิธีระบุความเสียหาย และวิธีปกป้อง ID
ID ที่มีการเปลี่ยนแปลงโดยหนึ่ง
ID ที่เปลี่ยนทีละรายการมักจะมาถึงว่า JSON ถูกต้องสมบูรณ์ ใส่ `{"orderId":9007199254740993}` ลงใน JavaScript และ `JSON.parse` ส่งคืนตัวเลขที่มีค่าที่แสดงเป็น `9007199254740992` การแยกวิเคราะห์สำเร็จเนื่องจากโทเค็นตามหลังไวยากรณ์ตัวเลข JSON ความเสียหายเกิดขึ้นขณะแปลงเลขทศนิยมเหล่านั้นเป็นการแสดงตัวเลขของ JavaScript ตัวจัดรูปแบบที่ทำให้ค่าที่แยกวิเคราะห์เป็นอนุกรมจะเขียนตัวเลขที่ปัดเศษอย่างแม่นยำ ไม่ใช่โทเค็นที่ปรากฏในแหล่งที่มา
ความแตกต่างจะเกิดขึ้นทันทีเมื่อมีการอ้างอิงตัวเลขเดียวกัน `JSON.parse("{"orderId":"9007199254740993"}")` ส่งคืนสตริง `9007199254740993` โดยคงอักขระทุกตัวไว้ และ `JSON.stringify` ปล่อยตัวเลขเหล่านั้นโดยไม่มีการเปลี่ยนแปลงภายในเครื่องหมายคำพูด นี่คือสาเหตุที่การตรวจสอบไวยากรณ์เพียงอย่างเดียวไม่สามารถป้องกันตัวระบุตัวเลขได้ เปรียบเทียบอินพุตและเอาต์พุตเมื่อใดก็ตามที่จำนวนเต็มยาวปรากฏขึ้น และถือว่าตัวระบุเป็นสตริงที่ขอบเขตการสร้างเมื่อเลขคณิตไม่ได้เป็นส่วนหนึ่งของความหมาย
สิ่งที่ RFC 8259 พูดเกี่ยวกับตัวเลข
RFC 8259 กำหนดการสะกดของตัวเลข JSON แต่ไม่ได้ให้การใช้งานทุกครั้งเป็นประเภทตัวเลขที่มีความแม่นยำตามอำเภอใจ ไวยากรณ์อนุญาตให้มีเครื่องหมายลบ ส่วนที่เป็นจำนวนเต็ม และส่วนเศษส่วนและเลขชี้กำลังก็ได้ ไม่รวมความสะดวก เช่น สัญกรณ์เลขฐานสิบหก `NaN` และ `Infinity` ด้วยเหตุนี้ `9007199254740993` จึงถูกต้องตามหลักไวยากรณ์ แม้ว่าผู้บริโภค JavaScript ทั่วไปจะไม่สามารถแสดงจำนวนเต็มนั้นเป็นตัวเลขได้ทุกประการ
คำแนะนำด้านการทำงานร่วมกันของข้อกำหนดคือคำเตือนในทางปฏิบัติ: โดยทั่วไปซอฟต์แวร์จะใช้ตัวเลข IEEE 754 binary64 และจำนวนเต็มในช่วงตั้งแต่ลบ `2^53 + 1` ถึงบวก `2^53 - 1` สามารถทำงานร่วมกันได้ในแง่ของข้อตกลงที่แน่นอน เครื่องมือตรวจสอบความถูกต้องสามารถยอมรับโทเค็นที่ใหญ่กว่าได้อย่างถูกต้องในขณะที่ parser จะปัดเศษโทเค็นในภายหลัง
โดยที่ 2^53 มาจากไหน
ขอบเขต `2^53` มาจากความแม่นยำที่มีอยู่ในซิกนิฟิแคนด์ไบนารี 64 JavaScript เปิดเผยจำนวนเต็มที่สามารถแทนค่าได้สูงสุดติดต่อกันเป็น `Number.MAX_SAFE_INTEGER` ซึ่งก็คือ `9007199254740991` ที่และต่ำกว่าขนาดดังกล่าว จำนวนเต็มที่อยู่ติดกันสามารถแสดงได้อย่างชัดเจน ด้านบน ระยะห่างระหว่างค่าที่แทนค่าได้จะเพิ่มขึ้น ดังนั้นจำนวนเต็มทศนิยมที่อยู่ใกล้เคียงจะแมปกับตัวเลขเดียวกัน รันไทม์ไม่ได้ตัดทอนสตริง เป็นการเลือกค่าที่ใกล้ที่สุดที่มีอยู่ในรูปแบบไบนารี่จำกัดนั้น
การตรวจสอบคอนโซลที่เปิดเผยคือ `Number.isSafeInteger(9007199254740993)` ซึ่งเป็นเท็จ แม้ว่าตัวอักษรต้นฉบับจะถูกปัดเศษก่อนที่ฟังก์ชันจะได้รับก็ตาม อีกประการหนึ่งคือ `9007199254740992 === 9007199254740993` ซึ่งประเมินค่าจริงใน JavaScript ตัวอย่างเหล่านี้เกี่ยวข้องกับการระบุจำนวนเต็มที่แน่นอน ไม่ว่าจำนวนที่มากกว่าทุกจำนวนจะใช้งานไม่ได้หรือไม่
การแยกวิเคราะห์และทำซ้ำจะสูญเสียตัวเลขอย่างไร
การจัดรูปแบบแยกวิเคราะห์และจัดเรียงซ้ำมีสามขั้นตอน: อ่านอักขระตัวเลข สร้างค่าในหน่วยความจำ จากนั้นสร้างอักขระใหม่จากค่านั้น รายละเอียดคำศัพท์จะหายไปที่ระดับกลาง ด้วย `{"ticket":9223372036854775807}` `JSON.parse` จะสร้างหมายเลข JavaScript ที่ใกล้เคียงที่สุดที่มีอยู่ `JSON.stringify` จากนั้นส่งเสียง `9223372036854776000` ซีเรียลไลเซอร์ไม่ได้สร้างความเสียหายให้กับโทเค็นที่เก็บรักษาไว้อย่างอิสระ เมื่อถึงเวลาซีเรียลไลเซชัน ลำดับดั้งเดิมของตัวเลขจะไม่ปรากฏในออบเจ็กต์ที่แยกวิเคราะห์อีกต่อไป
การใช้งานพื้นที่เก็บข้อมูลของ ToolAcre ใช้ `JSON.parse` และ `JSON.stringify` ดังนั้นข้อจำกัดนี้จึงนำไปใช้กับเอาต์พุตที่จัดรูปแบบแล้ว เครื่องสแกนไวยากรณ์ทำงานเพื่อให้เหตุผลและตำแหน่งที่มั่นคงหลังจากการแยกวิเคราะห์ล้มเหลว มันไม่ได้แทนที่ตัวเลข JavaScript ด้วยการนำเสนอที่มีความแม่นยำตามอำเภอใจ ผลการตรวจสอบที่ประสบความสำเร็จจะสร้างไวยากรณ์ ในขณะที่การจัดรูปแบบที่แตกต่างกันสามารถเปิดเผยการสูญเสียความแม่นยำได้
ตัวอย่างการทำงาน: การเปรียบเทียบอินพุตและเอาต์พุต
เปรียบเทียบ `{"numeric":9007199254740993,"text":"9007199254740993"}` ก่อนและหลังการเดินทางไปกลับ JavaScript การรัน `JSON.stringify(JSON.parse(source), null, 2)` จะสร้างอ็อบเจ็กต์ที่จัดรูปแบบแล้วซึ่งมีสมาชิก `numeric` คือ `9007199254740992` ในขณะที่ `text` ยังคงอยู่ `"9007199254740993"` สมาชิกทั้งสองถูกต้องในอินพุต และทั้งสองยังคงถูกต้องในเอาต์พุต เฉพาะการแสดงที่ยกมาเท่านั้นที่จะรักษาตัวระบุไว้อย่างแน่นอน เนื่องจากมันถูกถอดรหัสเป็นข้อมูลอักขระแทนที่จะเป็นตัวเลข
การตรวจสอบที่มีประโยชน์ไม่เพียงแต่ถามว่าฟอร์แมตเตอร์แสดงเป็นสีเขียวหรือไม่ ค้นหาแหล่งที่มาเพื่อดูลำดับตัวเลขที่ไม่ขาดตอน เปรียบเทียบค่าใดๆ ที่ยาวกว่าช่วงปลอดภัย และพิจารณาว่าแต่ละฟิลด์แสดงถึงปริมาณหรือป้ายกำกับทึบแสง หากผู้ผลิตควบคุมสัญญา ให้เปลี่ยนป้ายกำกับเป็นสตริงตรงนั้นและบันทึกตัวเลือกนั้นไว้สำหรับผู้บริโภค
การปกป้อง ID ที่ต้นทาง
ปกป้อง ID ที่ต้นทางโดยกำหนดเป็นสตริงในสคีมาและซีเรียลไลซ์เป็นสตริงก่อนที่ไคลเอ็นต์ JavaScript จะได้รับเพย์โหลด ID อาจมีเฉพาะตัวเลขและยังคงเป็นตัวเลขที่ไม่ใช่ตัวเลข: การบวก การปัดเศษ และการเรียงลำดับตามขนาดไม่ใช่การดำเนินการที่ถูกต้องกับคีย์บัญชี สตริงยังรักษาเลขศูนย์นำหน้าไว้ ซึ่งการแสดงตัวเลขจะละทิ้งแม้ว่าขนาดจะอยู่ในช่วงที่ปลอดภัยก็ตาม
อย่าสรุปความปลอดภัยข้ามภาษาจากข้อเท็จจริงที่ว่ารันไทม์อื่นสามารถเก็บจำนวนเต็มได้มากกว่าได้ ตัวแยกวิเคราะห์และประเภทเป้าหมายจะแตกต่างกันไป และตัวกลางที่เขียนด้วย JavaScript สามารถปัดเศษค่าก่อนที่บริการในภายหลังจะเห็นได้ parsers พิเศษบางคนเก็บโทเค็นตัวเลขหรือสร้างจำนวนเต็มขนาดใหญ่ แต่ผู้เข้าร่วมทุกคนต้องใช้สัญญานั้นร่วมกัน
สิ่งนี้ไม่ครอบคลุมถึง
สิ่งที่ไม่ครอบคลุมถึงคือการออกแบบเลขคณิตทศนิยมที่กว้างขึ้น ค่าต่างๆ เช่น `0.1` มีลักษณะการทำงานจุดลอยตัวแบบไบนารีของตัวเอง และเงินอาจต้องใช้จำนวนเต็มปรับขนาดหรือประเภททศนิยมตามสัญญาการสมัคร และการอ้างอิงทุกหมายเลขก็ไม่ได้ช่วยปรับปรุงสคีมาโดยอัตโนมัติ การนับ พิกัด และการวัดมักเป็นตัวเลขที่ถูกต้องตามกฎหมาย การตัดสินใจขึ้นอยู่กับว่าการสะกดทศนิยมหรือข้อมูลเฉพาะตัวของจำนวนเต็มที่แน่นอนจะต้องคงอยู่ตามผู้บริโภคทุกรายในเส้นทางข้อมูล
การสนทนานี้ไม่ได้อ้างว่า JSON ปัดเศษโทเค็นเอง หรือตัวแยกวิเคราะห์ทั้งหมดทำงานเหมือน JavaScript หลักฐานที่เก็บที่เป็นรูปธรรมนั้นแคบกว่า: ตัวจัดรูปแบบนี้เรียก `JSON.parse` และ `JSON.stringify` ดังนั้น JavaScript ความหมายของตัวเลขจะควบคุมค่าที่ไม่ได้ยกมาที่นี่ ไลบรารี JSON ที่มีความแม่นยำตามอำเภอใจสามารถสร้างตัวเลือกที่แตกต่างกันได้ แต่จะต้องกำหนดวิธีเปิดเผยค่าและซีเรียลไลซ์ค่า
ประเด็นสำคัญ: ตัวเลขที่สูงกว่า 2^53 อยู่ในสตริง
ประเด็นเฉพาะเจาะจงคือ ตัวระบุจำนวนเต็มที่อยู่นอกช่วงความปลอดภัยของ JavaScript จะอยู่ในสตริงเมื่อต้องผ่าน JavaScript โดยไม่เปลี่ยนแปลง `9007199254740993` เป็นตัวเลข JSON เป็นไวยากรณ์ที่ถูกต้อง แต่กลายเป็น `9007199254740992` หลังจาก `JSON.parse`; `"9007199254740993"` ยังคงเหมือนเดิม คำพูดไม่ใช่การตกแต่ง พวกเขาเลือกการนำเสนอที่รักษาตัวเลขไว้เป็นข้อมูลและป้องกันไม่ให้ผู้บริโภคถือว่าฉลากทึบแสงเป็นปริมาณโดยประมาณ
ก่อนที่จะแทนที่เอกสารด้วยเอาต์พุตของฟอร์แมตเตอร์ ให้เปรียบเทียบตัวเลขที่ยาวกับต้นฉบับ และตรวจสอบตัวเลขที่เปลี่ยนแปลงทุกหลัก แก้ไขโปรดิวเซอร์และสคีมาเมื่อเป็นไปได้ เพื่อให้ไคลเอนต์ดาวน์สตรีมทั้งหมดได้รับแบบฟอร์มที่ปลอดภัยอย่างสม่ำเสมอ ToolAcre สามารถเปิดเผยผลที่ตามมาได้เนื่องจากเอาต์พุตสะท้อนถึงค่า JavaScript ที่แยกวิเคราะห์แล้ว แต่ไม่สามารถสร้างตัวเลขที่สูญหายไปแล้วระหว่างการแยกวิเคราะห์ขึ้นมาใหม่ได้