JSON Toolkit

JSON เดียวกัน สี่เครื่องมือ สี่ผลลัพธ์: จะเลือกฟอร์แมตเตอร์ยังไง

สรุปสำคัญ: การจัดรูปแบบไม่ได้ไร้การสูญเสีย ข้อมูล JSON เดียวกันที่มีเลขนำหน้าด้วยศูนย์, BOM หรือจำนวนเต็ม 20 หลัก ให้ผลต่างกันใน Python, Node, jq และ json.tool: บางตัวรายงานข้อผิดพลาด บางตัวเปลี่ยนค่าเงียบๆ ตัวอย่าง 10 ชุดที่รันเองบนสี่เส้นทางให้ตารางเลือกเครื่องมือและสคริปต์ตรวจสอบแบบเข้มงวด

Intermediateอ่านประมาณ 6 นาทีเผยแพร่ 2026-11-03อัปเดต 2026-11-03
เส้นทางแยกวิเคราะห์สี่เส้นให้ผลต่างกันสำหรับ JSON เดียวกัน

สรุป: ความแตกต่างที่สำคัญที่สุดระหว่างเครื่องมือจัดรูปแบบ JSON ไม่ใช่จำนวนช่องเว้า แต่คืออินพุตเดียวกันที่เครื่องมือหนึ่งรายงานข้อผิดพลาด ขณะที่อีกเครื่องมือเงียบ ๆ เปลี่ยนข้อมูลไปแล้ว ก่อนเลือก ให้ลองส่งตัวอย่างที่มีค่าขอบผ่านเครื่องมือสักหนึ่งชุด — บอกอะไรคุณได้มากกว่ารายการฟีเจอร์ทั้งหมด

คนส่วนใหญ่คิดว่าการจัดรูปแบบคือแค่ «จัดวางข้อความใหม่» ความจริงแล้วทุกเส้นทางตีความข้อความนั้นใหม่ด้วย parser ของตัวเอง เมื่อ parser ผ่อนปรน ข้อผิดพลาดที่ควรถูกโยนออกมาก็กลายเป็นการแก้ไขค่าแบบเงียบ ๆ ขณะที่หน้าเว็บยังแสดงข้อความสีเขียว «จัดรูปแบบสำเร็จ» ที่ดูอุ่นใจอยู่เหมือนเดิม

1. ปรากฏการณ์: อินพุตเดียว สี่เส้นทาง สี่ผลลัพธ์

ทดสอบบน Python 3.11.1, Node v22.13.1 และ jq 1.7 ตัวอย่าง JSON สั้น 10 ชุดที่ประกอบขึ้นด้วยมือถูกส่งผ่าน standard input ไม่เขียนลงดิสก์ ไม่เปลี่ยนคอนฟิก สี่เส้นทางคือ json.loads, JSON.parse, jq -c ., python -m json.tool โดย ERR หมายถึงโยน exception แล้วหยุด ส่วน SILENT หมายถึงไม่มีข้อผิดพลาดแต่ผลลัพธ์ต่างจากอินพุตแล้ว

ตัวอย่าง python json.loads node JSON.parse jq -c . python -m json.tool
S1 {"a":1,"b":[1,2]} OK OK OK OK
S2 {"a":1,} ERR ERR ERR ERR
S3 {'a':1} ERR ERR ERR ERR
S4 {"a":01} ERR char 6 ERR position 6 SILENT {"a":1} ERR
S5 {"a":1 /*x*/} ERR char 7 ERR ERR ERR
S6 \ufeff{"a":1} ERR utf-8-sig ERR position 0 SILENT {"a":1} ERR
S7 {"id":9007199254740993} OK exact SILENT 9007199254740992 OK exact OK
S8 {"a":1,"a":2} SILENT {"a":2} SILENT {"a":2} SILENT {"a":2} OK
S9 {"a":1 ERR char 6 ERR ERR EOF ERR
S10 <html>502</html> ERR char 0 ERR Unexpected token '<' ERR ERR

สี่ในสิบตัวอย่างให้ผลแบบ «ไม่มีข้อผิดพลาดแต่ข้อมูลเปลี่ยน» คือ S4, S6, S7, S8 กล่าวคือถ้าเชื่อแค่ไฟเขียว «จัดรูปแบบสำเร็จ» คุณมีโอกาสร้อยละสี่สิบที่ถือข้อมูลที่ไม่ใช่ของเดิมอีกต่อไป สิ่งที่หลอกได้ก็คือกรณีเหล่านี้ไม่มีเลยในรายการข้อผิดพลาดทางไวยากรณ์ ไฟล์เปิดได้ คัดลอกได้ ส่งออกได้ ความผิดอยู่ในเนื้อหา ไม่ใช่ไวยากรณ์

2. สาเหตุ: ชั้นไวยากรณ์กับชั้นความหมายเป็นคนละเรื่องกัน

RFC 8259 พูดไว้ตรง ๆ ว่า หัวข้อ 1.2 แนะนำให้เลี่ยงวิธีปฏิบัติบางอย่างที่ ECMA-404 อนุญาต เพื่อให้ทำงานร่วมกันได้มากที่สุด หัวข้อ 4 กำหนดให้ชื่อออบเจกต์ควรไม่ซ้ำในระดับ SHOULD แต่ไม่ได้ระบุว่าจะจัดการคีย์ซ้ำยังไง หัวข้อ 6 ว่าด้วยตัวเลขให้เพียงคำแนะนำด้านการทำงานร่วมกัน ECMA-404 ยิ่งไปกว่านั้น ประกาศชัดว่า «ไม่ผูกมัดกับความหมายของตัวเลข» คือกำหนดแค่การเขียนลำดับตัวเลข ไม่กำหนดการเก็บในภาษา กล่าวอีกอย่างคือ สเปกปล่อยพื้นที่ขอบกว้าง ๆ ไว้ให้ผู้ implement เลือกเอง

ดังนั้น «การจัดรูปแบบ» จึงแยกเป็นสองชั้น ชั้นไวยากรณ์ตัดสินว่าข้อความถูกต้องตามกฎไหม ชั้นความหมายตัดสินว่าข้อความนั้นกลายเป็นค่าไหน ชั้นแรกมีคำตอบมาตรฐาน ชั้นหลังไม่มี ที่ jq อ่าน 01 เป็น 1 และข้าม BOM ต้นข้อความ คือการเลือกในชั้นความหมาย ที่ Node บีบจำนวนเต็ม 20 หลักลงใน double IEEE 754 คือการเลือกของ runtime ที่ทั้งสี่เส้นทางเก็บค่าสุดท้ายของ {"a":1,"a":2} เหมือนกัน คือธรรมเนียมร่วมของ parser ใหญ่ ๆ ไม่มีอันไหนละเมิด «parse ได้» แต่ทุกอันเปลี่ยนข้อมูล

วิธีแก้ JSON parse error จัดการชั้นไวยากรณ์ แปลข้อความ error ให้เป็นพิกัด บทความนี้จัดการขั้นตอนก่อนหน้านั้น ถ้าเครื่องมือไม่รายงาน error เลย คุณก็ไม่ได้แม้แต่พิกัด

3. การเปลี่ยนข้อมูลแบบเงียบสี่แบบ ทีละแบบ

แยกตัวอย่างเงียบทั้งสี่ออกมา การเปลี่ยนแปลงเล็กจนกวาดตาครั้งเดียวไม่ทันสังเกต

{"a": 01}                          // leading zero: jq emits {"a":1}
\ufeff{"a": 1}                     // BOM: jq skips the first three bytes
{"id": 9007199254740993}           // 20-digit int: JSON.parse gives ...992
{"a": 1, "a": 2}                   // duplicate key: all four keep {"a":2}

เลขศูนย์นำหน้ากับ BOM จัดอยู่แบบ «อินพุตไม่ถูกต้องอยู่แล้ว แต่เครื่องมือผ่อนปรนให้» 01 ผิดกฎใน JSON มาตรฐาน (RFC 8259 หัวข้อ 6) BOM ก็ไม่ควรอยู่ต้นข้อความ JSON Python กับ Node ต่างรายงาน error ชัดเจน แต่ jq เลือกเดินต่อ ความผ่อนปรนนี้สะดวกมากตอนล้าง log แต่หายนะตอนตรวจสัญญา ต้นทางคิดว่าส่งข้อมูลผิดที่ต้องแก้ ปลายทางก็ถือผลลัพธ์ «ที่ดูปกติ» อยู่แล้ว

จำนวนเต็มใหญ่จัดอยู่แบบ «อินพุตถูกต้องสมบูรณ์ แต่ runtime เก็บไม่ไหว» 9007199254740993 เป็นตัวเลข JSON ที่ถูกต้อง แต่เกิน 2^53-1 JSON.parse ทำได้แค่ 9007199254740992 ส่วน int ของ Python กับ jq รักษาค่าเดิมไว้ ถ้าใน JSON นี้เป็นเลขคำสั่งซื้อหรือ Snowflake ID ตัวเลขท้ายเปลี่ยนไปหนึ่งหลักก็หมายถึงยอดบัญชีที่กระทบกันไม่ได้แล้ว

คีย์ซ้ำจัดอยู่แบบ «สเปกไม่กำหนด implement เลือกเอง» ทั้งสี่เส้นทางเงียบ ๆ เอาค่าหลังสุด โดยไม่มีคำเตือนแม้แต่คำเดียว RFC 8259 บอกแค่ SHOULD ดังนั้นนี่ไม่ใช่บั๊ก แต่มันคือปัญหาที่หายากที่สุดตอนรวมคอนฟิกหรือ merge patch

4. ความสามารถระบุตำแหน่ง error: ใครให้บรรทัดคอลัมน์ ใครให้แค่ประโยคเดียว

ดูตัวอย่างที่ล้มเหลวทั้งหก ปริมาณข้อมูลที่สี่เส้นทางให้ต่างกันมาก JSONDecodeError ของ Python ให้ msg, lineno, colno, pos พร้อมกัน Node v22 รายงานพร้อม position N (line 1 column M) และข้อความต้นฉบับตรงจุดที่ผิด python -m json.tool ใช้ข้อมูลของ Python ส่วน jq ให้แค่ประโยค parse error: ... at line N ไม่มี offset ระดับอักขระ

กับ JSON บรรทัดเดียวที่ถูกย่อ หมายเลขบรรทัดกับคอลัมน์แทบไร้ประโยชน์ ทุกอย่างอยู่ในบรรทัด 1 สิ่งที่ต้องการคือ offset ระดับอักขระ สคริปต์ด้านล่างรวมการตรวจให้เป็น «error + offset + บริบท»

import json, sys

def strict_check(text: str) -> None:
    try:
        json.loads(text)
        print("OK")
    except json.JSONDecodeError as e:
        print(f"ERR {e.msg} | char {e.pos} | line {e.lineno} col {e.colno}")
        lo, hi = max(0, e.pos - 30), min(len(text), e.pos + 30)
        print("...", text[lo:hi].replace("\n", "\\n"), "...")
        sys.exit(1)

strict_check(open(sys.argv[1], encoding="utf-8").read())

มันเข้มงวดกว่า jq . (ปฏิเสธศูนย์นำหน้ากับ BOM) และให้ e.pos วิธีแปลง line 1 column 23611 เป็นตำแหน่งไบต์ และทำไม offset อักขระกับ offset ไบต์ในเอกสาร CJK ถึงต่างกันสามเท่า อธิบายไว้ละเอียดใน ดีบัก JSON ที่ถูกบีบอัด แล้ว ไม่ขอซ้ำที่นี่

5. แก้ยังไง: แยกการจัดรูปแบบเป็น «ตรวจเข้มงวด» + «ทำให้สวย»

สรุปง่าย ๆ คือ อย่าให้ปุ่ม «จัดรูปแบบ» ปุ่มเดียวแบกรับทั้งการตรวจและการทำให้สวย ให้ใช้ parser เข้มงวดตัดสินความถูกต้องก่อน ค่อยเลือกเครื่องมือจัดวาง ขั้นแรกแยกออกมาได้ด้วยคำสั่งบรรทัดเดียว

python3 -m json.tool input.json > /dev/null && echo "syntax OK" || echo "invalid syntax"
python3 -c "import json,sys; d=json.load(open(sys.argv[1],encoding='utf-8')); print(type(d).__name__, len(d))" input.json

บรรทัดแรกตอบแค่ «parse ได้ไหม» บรรทัดที่สองค่อยตรวจ «โครงสร้างที่ parse ออกมาใช่สิ่งที่คาดไหม» ถ้าข้อมูลมีจำนวนเต็มใหญ่ ให้อ่านด้วย Python หรือ jq อย่าไปทาง JSON.parse ถ้าต้นทางอาจมี BOM ให้ระบุ encoding='utf-8-sig' ตอนอ่านแทนที่จะหวังให้เครื่องมือข้ามให้เอง ถ้าคีย์อาจซ้ำ ต้องสแกนแยกก่อน parse เพราะทั้งสี่เส้นทางไม่มีอันไหนเตือนคุณ เขียนสิ่งเหล่านี้ลง CI ก็คือ pipeline ที่ Editor, Command Line และ Toolchain สำหรับ Automation อธิบาย เพียงเพิ่มชั้น assertion ความเที่ยงตรงของข้อมูลอีกชั้น

6. เลือกยังไง: ตารางเปรียบเทียบสี่เส้นทาง

เส้นทาง จุดแข็ง ความเสี่ยง เหมาะกับ
python json.loads / json.tool เข้มงวด ให้บรรทัด/คอลัมน์และ offset อักขระ จำนวนเต็มไม่เสียความแม่นยำ ปฏิเสธศูนย์นำหน้ากับ BOM ต้องจัดการ encoding เองชัดเจน ตรวจสัญญา, CI, สืบหา log
node JSON.parse เป็นเนทีฟในเบราว์เซอร์ error มีข้อความต้นฉบับแนบมา จำนวนเต็มใหญ่ถูกตัดเงียบ ๆ BOM ทำให้ error ดีบัก frontend, เครื่องมือในเบราว์เซอร์
jq -c . เข้ากับ pipe ดี ผลลัพธ์เร็วที่สุด ศูนย์นำหน้ากับ BOM ผ่านเงียบ ๆ ไม่มี offset อักขระ pipeline ปฏิบัติการ, ดึงฟิลด์เร็ว
เครื่องมือเบราว์เซอร์ /parse, /editor มองแบบต้นไม้ เรียงคีย์ คัดลอกพาธ ไม่ต้องติดตั้ง พฤติกรรมขึ้นกับ parser ของหน้าเว็บ ต้องทดสอบสักครั้ง ดูด้วยคน, หาข้อผิดพลาดเฉพาะหน้า

คำถามสำคัญไม่ใช่ «ใครมีฟีเจอร์มากกว่า» แต่คือ «เครื่องมือนี้โกหกเรื่องข้อมูลของฉันไหม» สำหรับข้อมูลที่มีเลขคำสั่งซื้อ จำนวนเงิน หรือ ID อุปกรณ์ ให้เลือก Python หรือเครื่องมือที่ประกาศชัดว่ารักษาข้อมูล ส่วนแค่ดูโครงสร้าง ไม่ได้เก็บลงฐาน เครื่องมือเบราว์เซอร์สะดวกที่สุด /parse ของไซต์นี้ตรวจไวยากรณ์และจัดรูปแบบในเบราว์เซอร์ เหมาะกับ «ขอดูหน้าตาก่อน» แต่พอมีจำนวนเต็มใหญ่ก็ยังแนะนำให้ทวนด้วยสคริปต์ข้างบนอีกครั้ง

7. ความเป็นส่วนตัวเป็นเพียงขอบเขตหนึ่ง

«ข้อมูลจะถูกอัปโหลดไหม» เป็นคำถามที่เจอบ่อยที่สุดกับเครื่องมือออนไลน์ แต่มันแยกจาก «ข้อมูลจะถูกเปลี่ยนไหม» เครื่องมือโลคอลที่ใช้ offline ได้ก็ยังตัดจำนวนเต็ม 20 หลักได้เหมือนกัน กลับกัน เครื่องมือออนไลน์ที่รายงาน error เข้มงวดมากก็อาจ POST ข้อความต้นฉบับขึ้นเซิร์ฟเวอร์ได้ สองอย่างนี้ต้องตรวจแยกกัน ไม่สามารถใช้แทนกันได้ FAQ ด้านความเป็นส่วนตัวให้สามวิธีตรวจในสองนาที บทความนี้เติมอีกครึ่งหนึ่ง ใช้รายการตัวอย่างด้านล่างยืนยันว่าเครื่องมือไม่เปลี่ยนค่าแบบเงียบ

8. รายการตรวจตัวเองที่คัดลอกไปใช้ได้

เก็บ S1–S10 เป็น cases.txt หนึ่งตัวอย่างต่อบรรทัด แปลงลงเครื่องมือที่กำลังพิจารณา แล้วเทียบสามอย่าง ตัวอย่างที่ควร error มัน error จริงไหม error บอกตำแหน่งชัดเจนไหม ผลลัพธ์ของตัวอย่างที่ผ่านตรงกับอินพุตทุกอักขระไหม โดยเฉพาะสี่บรรทัด S4, S6, S7, S8 แค่มีบรรทัด SILENT บรรทัดเดียวก็แปลว่าเส้นทางนั้นใช้ตรวจข้อมูลคนเดียวไม่ได้

เพิ่มกฎวิศวกรรมสามข้อ ใส่การตรวจเข้มงวดลง pre-commit hook อย่ารอไปเจอจำนวนเต็มคลาดเคลื่อนตอน production เขียนไฟล์เป็น UTF-8 ไม่มี BOM ให้สอดคล้องกันทุกภาษา สำหรับ JSON จากบุคคลที่สามให้เก็บ response body ดิบ ๆ ไว้ ส่วนผลที่จัดรูปแบบแล้วใช้เพื่ออ่านเท่านั้น ไม่ใช้เขียนกลับ

คำถามที่พบบ่อย

จัดรูปแบบสำเร็จ แปลว่า JSON ไม่มีปัญหาแล้วใช่ไหม

ไม่ใช่ S4, S6, S7, S8 ล้วน «ผ่าน» การจัดรูปแบบ แต่ศูนย์นำหน้า BOM จำนวนเต็ม 20 หลัก และคีย์ซ้ำถูกเขียนทับหรือกลืนหายไป การจัดรูปแบบพิสูจน์แค่ว่ามี parser สักตัวรับข้อความ ไม่ได้พิสูจน์ว่าค่าหลัง parse เท่ากับค่าที่คุณส่ง ฟิลด์สำคัญต้องตรวจเป็นรายค่า

ทำไม jq ถึงรับศูนย์นำหน้ากับ BOM ที่ Python ปฏิเสธ

jq มี lexer ของตัวเอง ผ่อนปรนกับตัวเลขอย่าง 01 และข้าม BOM ต้นข้อความ ส่วน Python กับ Node ยึด RFC 8259 เข้มงวด นี่ไม่ใช่บั๊กของเวอร์ชัน แต่คือทางเลือกระหว่าง parser ที่อดทนกับ parser ที่เข้มงวด ตอนล้างข้อมูลแบบแรกประหยัดงาน ตอนตรวจสัญญาแบบหลังจำเป็น

จะป้องกันไม่ให้จำนวนเต็มใหญ่กลายเป็น 9007199254740992 ได้ยังไง

Number ใน JavaScript คือ double IEEE 754 ที่ขีดจำกัดปลอดภัยอยู่ที่ 2^53-1 การอ่าน ID 20 หลักด้วย JSON.parse ย่อมถูกตัด ให้เก็บ ID แบบนี้เป็น string หรือใช้ int ของ Python, jq หรือไลบรารี parse ที่รองรับ BigInt ถ้า frontend จำเป็นต้องได้ตัวเลขจริง ๆ ให้แทนที่ฟิลด์นั้นด้วย string ก่อน parse

จะใช้เครื่องมือออนไลน์หรือบรรทัดคำสั่งโลคอลดี

แยกตามความอ่อนไหวและจุดประสงค์ แค่ดูโครงสร้าง หาฟิลด์ เบราว์เซอร์เร็วที่สุด ถ้าจะเข้าสาย pipeline และต้อง assertion ความเที่ยงตรงของข้อมูล ใช้ python -m json.tool บวกสคริปต์ตรวจเข้มงวด ทั้งสองแบบควรลอง S1–S10 ก่อน เทียบ error และผลลัพธ์กับที่คาด แล้วค่อยตัดสินใจว่าจะป้อนข้อมูลระดับไหนเข้าไป