JSON บรรทัดเดียว กับปลายทางสี่แห่ง
gateway ของระบบออเดอร์รับ body ใบหนึ่งเข้ามาทาง HTTP ตรวจสิทธิ์ แล้วส่งต่อให้ service ข้างหลัง มันไม่ได้มีหน้าที่แก้อะไรใน body นั้นเลยสักหลัก
body ใบนั้นมีสองช่องที่บทนี้ตามไปดู คือ orderId กับ total ซึ่งเป็นยอดที่เก็บเป็นจำนวนเต็ม
ของหน่วยย่อย ทั้งสองช่องเป็นตัวเลขล้วน ไม่ได้ครอบด้วยเครื่องหมายคำพูด
{"orderId": 9007199254740993, "total": 12345678901234567890}คำถามของบทนี้ไม่ใช่ว่าภาษาไหนอ่าน JSON ได้ถูกกว่ากัน แต่เป็นว่า body ที่ออกจาก gateway ยังเป็น body ใบเดิมหรือเปล่า และอะไรเป็นคนตัดสิน คำตอบที่วัดได้คือชนิดปลายทางของฝั่งที่อ่าน
orderctl คือเครื่องมือประมวลผลออเดอร์ที่คอร์สภาษาในเสานี้สร้างซ้ำทีละภาษา repo ตัวอย่าง
กำลังจัดทำ ระหว่างนี้ทุกตัวเลขในบทนี้รันเองได้ด้วย npm run verify:integer-semantics
ซึ่งเรียก python3 scripts/integer-semantics/probe.py · ไม่ต้องมี Docker และไม่ต้องต่อเน็ต
body ใบเดียว แปดแถวที่วัดได้
หัวข้อที่มีชื่อว่า “body ใบเดียว แปดแถวที่วัดได้”แปดแถวนี้มาจาก body บรรทัดเดียวกัน และความต่างทั้งหมดเกิดหลังจากที่ body เข้าไปในโปรแกรมแล้ว
สเปกของ JSON ไม่ได้กำหนดความกว้างให้ตัวเลขไว้เลย JSON numberJSON numberชนิดตัวเลขของ JSON ที่สเปกไม่ได้กำหนดความกว้างไว้ · ค่าที่ได้จริงจึงถูกตัดสินโดยชนิดปลายทางของฝั่งที่อ่าน ไม่ใช่โดยตัวข้อความที่ส่งมา จึงเป็นเพียง ลำดับของหลัก ส่วนค่าที่ได้จริงมาจากชนิดที่ฝั่งอ่านเตรียมไว้รับมัน
| ปลายทาง | orderId | total |
|---|---|---|
Node JSON.parse | 9007199254740992 | 12345678901234567000 |
Node เขียนกลับด้วย JSON.stringify | {"orderId":9007199254740992,"total":12345678901234567000} | — |
Python json.loads | 9007199254740993 ชนิด int | 12345678901234567890 |
Python json.dumps | {"orderId": 9007199254740993, "total": 12345678901234567890} | — |
Go map[string]any | 9.007199254740992e+15 ชนิด float64 | 1.2345678901234567e+19 |
Go map[string]any เขียนกลับด้วย Marshal | {"orderId":9007199254740992,"total":12345678901234567000} | — |
Go struct{ OrderId, Total int64 } | 9007199254740993 | 0 และมี error |
Go Decoder ที่ตั้ง UseNumber() | 9007199254740993 | 12345678901234567890 |
สามแถวที่ช่องขวาเป็น — คือแถวของการเขียนกลับ ค่าที่วัดได้ของแถวพวกนั้นเป็น body ทั้งใบ
จึงอยู่ในช่องเดียว ไม่ได้แยกเป็นสองช่องเหมือนแถวที่เหลือ
ห้าแถวที่เหลือเป็นแถวของการอ่าน และค่าของทั้งห้าแถวตกลงในชนิดปลายทางสี่ชนิด คือ float64
ของ map กับ number ของ Node ซึ่งเป็น double 64 bit ตัวเดียวกัน · int64 ของ struct ·
json.Number ของ Decoder และ int ของ Python · ชื่อบทนับสี่ชนิดนี้ ไม่ได้นับจำนวนแถว
แถวที่ 2 กับแถวที่ 6 น่าสนใจที่สุดเมื่อวางคู่กัน probe เทียบสองสตริงนั้นตรงๆ แล้วได้ว่า
Node กับ map ของ Go เขียน body ใหม่ออกมาเหมือนกันทุกตัวอักษร ทั้งที่เป็นคนละภาษา
โปรแกรม Go ใบเดียว ให้ผลสามแบบ
หัวข้อที่มีชื่อว่า “โปรแกรม Go ใบเดียว ให้ผลสามแบบ”ภาษาเดียว ตัว decoder ตัวเดียว body ใบเดียว ต่างกันที่ชนิดที่รับค่าไว้เท่านั้น
สามทรงข้างล่างนี้อยู่ในโปรแกรมเดียวกัน รันต่อกันในการเรียกครั้งเดียว และรับ body ตัวแปร
เดียวกันทั้งสามทรง
type Order struct { OrderId int64 `json:"orderId"` Total int64 `json:"total"`}ผลที่ออกมาต่างกันสามแบบ ส่วนค่าของ orderId ที่ได้จากสามทรงนี้ต่างกันสองแบบ เพราะสองทรงหลัง
ให้ค่าเดียวกันตรงทุกหลัก
ทรงที่หนึ่ง · map[string]any ที่ไม่มีใครประกาศชนิด
หัวข้อที่มีชื่อว่า “ทรงที่หนึ่ง · map[string]any ที่ไม่มีใครประกาศชนิด”เอกสารของ encoding/json เขียนกฎนี้ไว้ตรงๆ คือเมื่อปลายทางเป็น interface value ตัว
Unmarshal จะเก็บ float64 ลงไปให้ทุกตัวเลขของ JSON · json.Unmarshal ไม่มีสวิตช์ให้
เปลี่ยนข้อนี้ สวิตช์อยู่บน Decoder ซึ่งเป็นทรงที่สาม
var m map[string]any out["mapErr"] = errText(json.Unmarshal(body, &m)) out["mapOrderId"] = fmt.Sprintf("%v", m["orderId"]) out["mapTotal"] = fmt.Sprintf("%v", m["total"]) out["mapOrderIdType"] = fmt.Sprintf("%T", m["orderId"]) re, _ := json.Marshal(m) out["mapRemarshal"] = string(re)%T ตอบว่า float64 และ %v ตอบว่า 9.007199254740992e+15 ซึ่งเป็นค่าที่เสียหลักไปแล้ว
ตั้งแต่ตอนที่ Unmarshal คืนค่า ส่วน err ที่ Unmarshal คืนมาเป็นสตริงว่าง
total เป็นรูปเดียวกัน %v ตอบว่า 1.2345678901234567e+19 ซึ่งไม่ใช่ยอดที่ต้นทางส่งมา
และไม่มีอะไรในโปรแกรมบอกว่ามันเคยเป็นค่าอื่น
ทรงที่สอง · struct ของ int64 ที่เติมไปแล้วครึ่งใบ
หัวข้อที่มีชื่อว่า “ทรงที่สอง · struct ของ int64 ที่เติมไปแล้วครึ่งใบ” var o Order out["structErr"] = errText(json.Unmarshal(body, &o)) out["structOrderId"] = fmt.Sprintf("%d", o.OrderId) out["structTotal"] = fmt.Sprintf("%d", o.Total)OrderId ได้ 9007199254740993 ตรงทุกหลัก เพราะ int64 กว้างพอสำหรับค่านี้ ส่วน Total
ได้ 0 เพราะ 12345678901234567890 เกินช่วงของ int64 และ err ไม่ใช่ค่าว่าง
json: cannot unmarshal number 12345678901234567890 into Go struct field Order.total of type int64นี่คือ partial unmarshalpartial unmarshalการที่ตัวถอดค่าเติม struct ได้บางช่อง แล้วคืน error ออกมาพร้อมกัน · ผู้เรียกที่บันทึก error แล้วไปต่อ จึงถือ struct ที่เต็มครึ่งเดียวอยู่ในมือโดยไม่รู้ตัว · เมื่อ Unmarshal คืนค่ามา err ไม่เป็น
nil แต่ OrderId ถูกเติมไปเรียบร้อยแล้ว struct ที่อยู่ในมือจึงเต็มครึ่งเดียว
เอกสารของ encoding/json บอกไว้ว่าเมื่อตัวเลขของ JSON ล้นชนิดปลายทาง Unmarshal จะข้ามช่อง
นั้นแล้วถอดค่าต่อให้ได้มากที่สุดเท่าที่ทำได้
ประโยคถัดไปมีเงื่อนไขนำหน้าอยู่ คือ If no more serious errors are encountered แล้วจึงบอกว่า
มันคืน UnmarshalTypeError ของความผิดพลาดที่เกิดก่อนที่สุด
UnmarshalTypeError จึงไม่ใช่สิ่งที่รับประกันว่าจะได้คืนมาทุกครั้ง มันเป็นสิ่งที่ได้คืนมาเมื่อ
ไม่มีความผิดพลาดที่หนักกว่านั้นเกิดขึ้น
มันยังเขียนต่อด้วยว่าไม่มีการรับประกันว่าช่องที่เหลือซึ่งตามหลังช่องที่มีปัญหาจะถูกถอดค่าลง ปลายทางครบ ประโยคนั้นถอนการรับประกันออก มันไม่ได้บอกว่าช่องที่ตามหลังจะว่าง
สถานะของ struct ที่เหลือจึงเป็นสิ่งที่ต้องวัด ไม่ใช่สิ่งที่อ่านออกมาจากเอกสารได้ · probe ของ
คอร์สนี้วัดไว้ทรงเดียว คือ body ใบนี้ที่ orderId มาก่อน total
ผู้เรียกที่เขียน if err != nil { slog.Warn("unmarshal", "err", err) } แล้วเดินต่อ จึงถือออเดอร์ที่มี id ถูกต้อง
คู่กับยอดเป็น 0 ซึ่งเป็นคู่ที่หน้าตาสมเหตุสมผลกว่าการไม่มีข้อมูลเลย
ทรงที่สาม · Decoder ที่ตั้ง UseNumber()
หัวข้อที่มีชื่อว่า “ทรงที่สาม · Decoder ที่ตั้ง UseNumber()” var n map[string]any dec := json.NewDecoder(bytes.NewReader(body)) dec.UseNumber() out["numberErr"] = errText(dec.Decode(&n)) out["numberOrderId"] = fmt.Sprintf("%v", n["orderId"]) out["numberTotal"] = fmt.Sprintf("%v", n["total"]) out["numberOrderIdType"] = fmt.Sprintf("%T", n["orderId"])%T ตอบว่า json.Number และทั้งสองช่องออกมาตรงทุกหลัก คือ 9007199254740993 กับ
12345678901234567890 ส่วน err ที่ Decode คืนมาเป็นสตริงว่าง
เอกสารบอกว่า UseNumber ทำให้ Decoder ถอดตัวเลขลง interface value เป็น Number แทนที่จะ
เป็น float64 ปลายทางจึงเปลี่ยนไปทั้งที่ body ไม่ได้เปลี่ยน
json.Number คือ string ที่เก็บหลักไว้ครบ ไม่ใช่ชนิดจำนวนเต็ม การแปลงมันลง int64 เป็นขั้น
ที่คุณต้องเรียกเอง และ probe ของคอร์สนี้ไม่ได้วัดขั้นนั้น
สิ่งที่ UseNumber ให้คือการเลื่อนพรมแดนออกไป จากจุดที่ decoder ตัดสินใจแทนคุณ มาอยู่ที่จุดที่
คุณเขียนการแปลงเอง ราคาของมันคือทุกที่ที่เคยอ่านค่าออกมาใช้ได้เลย ต้องเติมขั้นแปลงเข้าไป
การเขียนกลับที่เงียบ และยังเป็น JSON ที่ถูกต้อง
หัวข้อที่มีชื่อว่า “การเขียนกลับที่เงียบ และยังเป็น JSON ที่ถูกต้อง”ความเสียหายไม่ได้อยู่ที่การอ่าน แต่อยู่ที่การเขียนค่าที่อ่านมาแล้วส่งต่อไปข้างหน้า
gateway ที่อ่าน body ลง map[string]any แล้ว Marshal กลับออกไป คือรูปที่ไม่ต้องประกาศ
struct ให้ทุก body ที่ผ่านมือ และเป็นทรงที่หนึ่งของสามทรงที่หัวข้อข้างบนวัดไว้
{"orderId":9007199254740992,"total":12345678901234567000}สิ่งที่ออกมาเป็น JSON ที่ถูกต้องทุกประการ ทั้งสองช่องยังเป็นตัวเลข ยังมีชื่อคีย์เดิม และยังส่ง เข้า service ข้างหลังได้โดยไม่มีใครปฏิเสธมัน
แต่ orderId ที่ออกมาน้อยกว่าที่รับเข้ามาอยู่หนึ่ง และ total ที่ออกมาลงท้ายด้วยศูนย์สามตัว
แทนหลักสามหลักสุดท้ายที่รับเข้ามา ทั้งสองค่าเปลี่ยนโดยที่ err ของ Unmarshal เป็นสตริงว่าง
err ตัวนั้นคือ error ตัวเดียวที่ probe บันทึกไว้ในเส้นทางนี้ เพราะ runner เขียนขั้นเขียนกลับ
เป็น re, _ := json.Marshal(m) ซึ่งทิ้ง error ของขั้นนั้นไป สิ่งที่วัดได้ของขั้นนั้นจึงเป็น
ไบต์ที่มันคืนมา คือ body เต็มใบ ไม่ใช่ค่าว่างที่ Marshal คืนเมื่อมันล้มเหลว
Number.isSafeInteger(9007199254740993) ของ Node ตอบว่า false ซึ่งเป็นคำตอบที่ตรงคำถามนี้
ที่สุด แต่ไม่มี runtime ไหนเรียกมันให้เอง มันเป็นด่านที่ทำงานก็ต่อเมื่อมีคนเรียก
flowchart TD B["body เดียว · orderId 9007199254740993 · total 12345678901234567890"] --> G["Go"] B --> N["Node · JSON.parse"] B --> P["Python · json.loads"] G --> M["ปลายทาง map[string]any"] G --> S["ปลายทาง struct ของ int64"] G --> U["ปลายทาง Decoder ที่ตั้ง UseNumber"] M --> M2["float64 · เขียนกลับได้ orderId 9007199254740992 · Unmarshal ไม่คืน error"] S --> S2["orderId ตรง · total เป็น 0 · มี error พร้อม struct ที่เติมครึ่งใบ"] U --> U2["json.Number · ตรงทั้งสองช่อง · ไม่มี error"] N --> N2["number ที่เป็น double · เขียนกลับได้ orderId 9007199254740992"] P --> P2["int · เขียนกลับได้ body เดิมทุกหลัก"]
คำบรรยายภาพ: กิ่งของ Go แตกเป็นสามกิ่งย่อยก่อนถึงผลลัพธ์ จุดที่แตกคือชนิดปลายทาง ไม่ใช่ชื่อ ภาษา · สองช่องที่เขียนกลับออกมาได้ orderId เป็น 9007199254740992 คือสองช่องที่ค่าเปลี่ยนไป โดยไม่มีใครรู้ตัว
Node เสีย id แบบเดียวกัน ส่วน Python ไม่เสีย
หัวข้อที่มีชื่อว่า “Node เสีย id แบบเดียวกัน ส่วน Python ไม่เสีย”สองภาษานี้รับ body ใบเดียวกัน และทั้งคู่มีขั้นเขียนกลับเหมือนกัน แต่สิ่งที่เขียนออกมาไม่เท่ากัน
JSON.parse ของ Node คืน 9007199254740992 ให้ orderId และ 12345678901234567000 ให้
total เพราะมันถอดตัวเลขของ JSON ลง Number ซึ่งเป็น double ตัวเดียวกับที่ map ของ Go ได้มา
JSON.stringify ที่ตามมาจึงเขียนค่าที่เปลี่ยนไปแล้วออกไป ไม่ได้เขียนข้อความที่รับเข้ามา
ส่วนที่ผลของมันตรงกับ map ของ Go ทุกตัวอักษร ไม่ใช่เรื่องบังเอิญ ทั้งสองทางถือ double ค่าเดียวกัน
แล้วเขียนมันกลับออกมาเป็นหลักล้วนเหมือนกัน จุดที่ตรงกันคือชนิดที่รับค่าไว้ ไม่ใช่ภาษา
json.loads ของ Python คืน 9007199254740993 ชนิด int และคืน 12345678901234567890
ตรงทุกหลัก แล้ว json.dumps ก็เขียน body เดิมกลับออกมาไม่เปลี่ยนสักหลัก
เอกสารของ Python เขียนตารางการแปลงไว้ว่า JSON number ที่เป็นจำนวนเต็มไปเป็น int และระบุว่า
ค่าตั้งต้นของ parse_int เท่ากับการเรียก int(num_str) กับสตริงของทุกจำนวนเต็มที่ถอดค่า
int ของ Python ไม่มีความกว้าง ซึ่งเป็นข้อที่บทที่ 2 วัดไว้แล้ว ช่องนี้จึงตรงเพราะสองข้อประกอบกัน
คือตัวถอดค่าส่งตัวเลขจำนวนเต็มลง int ตามตารางข้างบน และ int ไม่มีขอบให้ค่าชนกับมัน
ทั้งสองข้อเป็นเรื่องของชนิดปลายทาง ไม่ใช่เรื่องที่ parser ของมันระวังกว่า
API ของ encoding/json เป็นของบทที่ 3 ในคอร์ส
Go จากศูนย์ คือ struct tag การจับคู่คีย์
Decoder เทียบ Unmarshal DisallowUnknownFields และ omitempty
ตารางของบทนั้นมีแถวหนึ่งที่บอกว่าตั้ง UseNumber() แล้วได้ json.Number แทน float64
เมื่ออ่านลง any · แถวนั้นบอกว่าสวิตช์มีอยู่ แต่ไม่ได้วัดค่าไหนเลย
บทนี้ไม่ได้ทัวร์ API ซ้ำ บทนี้เป็นเจ้าของค่าที่หายไประหว่างทาง คือตัวเลขที่เข้าไปกับตัวเลขที่ ออกมาไม่เท่ากัน และเป็นเจ้าของคำถามว่าใครเป็นคนตัดสินค่านั้น
ความเสียหาย · gateway ที่เขียน id ใหม่ให้ทุกใบ
หัวข้อที่มีชื่อว่า “ความเสียหาย · gateway ที่เขียน id ใหม่ให้ทุกใบ”gateway ไม่ได้คำนวณอะไรสักครั้ง มันแค่อ่าน body แล้วเขียนกลับ และ id ที่ออกไปไม่ใช่ id ที่เข้ามา
บทที่ 5 วัดค่าที่เปลี่ยนตอนโปรแกรมเขียนการแปลงลงไปเอง ส่วนที่นี่ไม่มีใครเขียนการแปลงสักบรรทัด
orderId ที่ออกจาก gateway เป็น 9007199254740992 ทั้งที่ทุกบรรทัดใน gateway อ่านแล้วส่งต่อ
service ข้างหลังจึงได้ id ที่ไม่ตรงกับที่ลูกค้ากดมา และคำสั่งที่ตามมาก็ทำงานกับ id ใบนั้น ตามปกติ ไม่มีขั้นไหนในเส้นทางนี้คืน error ให้ใครเห็น
ยอดเป็นรูปเดียวกันแต่ตามรอยยากกว่า เพราะ 12345678901234567000 ยังอ่านดูเหมือนยอดปกติ
ไม่มีหลักไหนหายไปทั้งก้อน มีแต่หลักท้ายที่กลายเป็นศูนย์
การไล่หาสาเหตุจะเริ่มที่ service ข้างหลังก่อนเสมอ เพราะที่นั่นคือที่ที่อาการโผล่ ส่วนจุดที่ค่า เปลี่ยนอยู่ที่ gateway ซึ่งเป็นที่เดียวในเส้นทางที่ไม่มีใครสงสัย มันแค่ส่งต่อ
สามทางข้างล่างแก้ที่ gateway ได้ทั้งหมด และแต่ละทางย้ายงานไปวางไว้ที่อื่น
- ส่งต่อ body เป็นไบต์ดิบ ไม่ต้องถอดค่าเลยถ้าไม่ต้องอ่านช่องไหน ราคาคือ gateway จะตรวจ หรือแก้อะไรใน body ไม่ได้อีกเลย และกฎที่เคยเขียนไว้ตรงนั้นต้องย้ายไปที่อื่น
- ตั้ง
UseNumber()แล้วอ่านเป็นjson.Numberราคาคือทุกจุดที่เคยใช้ค่าได้ทันทีต้อง เติมขั้นแปลงเอง และต้องตัดสินใจเองว่าค่าที่แปลงไม่ผ่านจะไปทางไหน - ประกาศ struct ให้ทุก body ที่ผ่านมือ ราคาคือต้องเขียนชนิดตามให้ครบทุกช่องที่ต้นทาง
เพิ่มเข้ามา และต้องอ่าน
errทุกครั้ง ไม่ใช่บันทึกไว้แล้วเดินต่อ
ข้อสุดท้ายมีกับดักซ้อนอยู่ การประกาศ struct ไม่ได้ทำให้ค่าที่ล้นกลายเป็นค่าที่ถูก มันเพียงทำให้ มีคนบอกคุณ · คนที่บันทึก error แล้วเดินต่อจะถือ struct ที่เต็มครึ่งเดียวเหมือนเดิม
จุดที่ห้าในห้าจุด · ครึ่งที่ชนิดปลายทางตัดสิน
หัวข้อที่มีชื่อว่า “จุดที่ห้าในห้าจุด · ครึ่งที่ชนิดปลายทางตัดสิน”บทนี้เปิดจุดที่ห้า และเป็นจุดที่ค่าเปลี่ยนโดยไม่มีการคำนวณและไม่มีการแปลงที่คุณสั่งเอง
ธีสิสของคอร์สนี้คือ ค่าจำนวนเต็มค่าหนึ่งเปลี่ยนได้ห้าจุดระหว่างทางผ่านระบบ และทุกจุดในห้าจุดนั้น เงียบ สี่จุดแรกคือความกว้างที่หมด ค่าที่อยู่นอกชนิดเดิม ทิศของการปัด และทศนิยมที่กลืนจำนวนเต็ม
จุดที่ห้าคือ พรมแดนของการ parseparse boundaryจุดที่ค่าจากภายนอกกลายเป็นค่าในชนิดของภาษา · นโยบายเมื่อ parse ไม่สำเร็จมีสี่แบบ คือโยน คืนค่าพร้อม error ตัดท้ายทิ้งเงียบๆ และตรงเป๊ะ ซึ่งเป็นจุดที่ค่าจากภายนอกกลายเป็นค่าใน ชนิดของภาษา สามจุดแรกต้องมีการคำนวณที่เขียนอยู่ในบรรทัด ส่วนจุดที่สี่ยังมีการแปลงหรือชื่อของ ชนิดทศนิยมให้เห็น จุดนี้ไม่มีทั้งสองอย่าง สิ่งเดียวที่คุณเขียนคือชนิดของตัวแปรที่รับค่า
สิ่งที่ตัดสินค่าที่ออกมาไม่ใช่ตัวข้อความที่ส่งมา เพราะข้อความเดียวกันให้คำตอบคนละแบบ และไม่ใช่ ภาษา เพราะโปรแกรม Go ใบเดียวให้ผลสามแบบ สิ่งที่ตัดสินคือชนิดที่ฝั่งอ่านเตรียมไว้รับมัน
บทที่ 7 อยู่ที่จุดที่ห้าเหมือนกัน แต่เปลี่ยนรูปของอินพุต จากตัวเลขที่อยู่ใน JSON ไปเป็นสตริง ที่ถูกสั่งให้กลายเป็นตัวเลขตรงๆ แล้ววัดว่าแต่ละภาษาทำอะไรเมื่อมันแปลงไม่สำเร็จ
-
Go package documentation —
encoding/json(เข้าถึง 2026-09-13) — หัวข้อUnmarshalระบุว่าเมื่อปลายทางเป็น interface value ตัวUnmarshalเก็บfloat64ลงไปให้ JSON number · และระบุว่าเมื่อค่าไม่เหมาะกับชนิดปลายทาง หรือตัวเลขล้นชนิดปลายทาง มันจะข้ามช่องนั้นแล้วถอดค่าต่อให้ได้มากที่สุดเท่าที่ทำได้ · ประโยค ถัดไปขึ้นต้นด้วยเงื่อนไข If no more serious errors are encountered แล้วจึงบอกว่ามันคืนUnmarshalTypeErrorของความผิดพลาดที่เกิดก่อนที่สุด · และระบุต่อว่าไม่รับประกันว่าช่องที่ ตามหลังช่องที่มีปัญหาจะถูกถอดค่าครบ · หัวข้อDecoder.UseNumberระบุว่ามันทำให้Decoderถอดตัวเลขลง interface value เป็นNumberแทนที่จะเป็นfloat64 -
Number.isSafeInteger()— MDN (เข้าถึง 2026-09-13) — นิยาม safe integer ไว้สองข้อ คือแทนค่าได้ตรงตัวใน IEEE 754 และการ แทนค่านั้นต้องไม่ใช่ผลของการปัดจำนวนเต็มค่าอื่นมา · ยกตัวอย่างว่า2 ** 53ไม่ใช่ safe integer เพราะ2 ** 53 + 1แทนตรงตัวไม่ได้ แล้วถูกปัดมาเป็น2 ** 53 -
json— JSON encoder and decoder, Python documentation (เข้าถึง 2026-09-13) — ตารางการแปลงของJSONDecoderจับคู่ JSON number ที่เป็นจำนวนเต็ม ไว้กับintของ Python · และระบุว่าเมื่อไม่ได้ตั้งparse_intค่าตั้งต้นเทียบเท่ากับการ เรียกint(num_str)กับสตริงของทุกจำนวนเต็มที่ถอดค่า -
ตัวเลขทุกตัวในบทนี้มาจาก
scripts/integer-semantics/measured.jsonซึ่งscripts/integer-semantics/probe.pyเขียนขึ้นหลังยิงทั้งชุดสามรอบต่อการเรียกหนึ่งครั้ง · วัดบนgo1.27.0node v24.19.0และPython 3.14.6บน darwin/arm64 เมื่อ 2026-09-12
เช็กความเข้าใจ — บทที่ 6
ข้อ 1 / 3โปรแกรม Go อ่าน body ใบนี้ลง `map[string]any` แล้ว `Marshal` กลับออกไป ผลที่วัดได้เป็นอย่างไร