ความต่างระหว่าง JSON Formatting·Validation·JSON Schema และการใช้ในงานจริง
สรุป (TL;DR)
เดือนที่แล้วทีมหนึ่ง deploy feature_flags.json ที่แก้ไปเพียงบรรทัดเดียว JSON.parse ผ่าน CI เขียวทั้งหมด พอส่งขึ้น staging ปรากฏว่า flags.checkout_v2 เป็น string "true" ทำให้ทุก branch ของการชำระเงินตกไปที่เวอร์ชันเก่า คำว่า “ตรวจสอบ JSON แล้ว” แท้จริงเป็นคำรวมของงาน 3 อย่าง คือ การจัดรูปแบบ การตรวจ syntax และการตรวจโครงสร้าง ในวันนั้นทีมทำแค่สองขั้นตอนแรก การจัดรูปแบบ (pretty-print) เพียงแทรก whitespace และ newline ให้อ่านง่าย ไม่ validate อะไร การตรวจ syntax ตามมาตรฐาน RFC 8259 ดูวงเล็บ เครื่องหมายคำพูด และ escape ว่าถูกไหม ซึ่ง JSON.parse ทำหน้าที่นี้ การตรวจโครงสร้าง (structural validation) เกิดหลังจากนั้น ถามว่า “รูปร่างนี้ตรงกับที่โค้ดคาดหวังไหม” และ JSON Schema ถูกสร้างมาเพื่อสิ่งนี้ ทันทีที่คิดรวม 3 ขั้นตอนเป็น “ปุ่ม validate JSON ปุ่มเดียว” ก็จะเกิดความล้มเหลวเงียบแบบกรณีข้างต้น เวลาอ่านใช้ formatting เวลารับ input ใช้ parsing เวลาผ่านขอบเขตความเชื่อถือเช่น API boundary, config file, message boundary ใช้ validator อย่าง Ajv ตรวจ schema ด้วย pipeline ที่วาง 3 ขั้นตอนให้อยู่ในที่ของตัวเองจึงปลอดภัย
ภูมิหลังและแนวคิด
JSON ถูกกำหนดโดย RFC 8259 (และ ECMA-404 ที่เทียบเท่า) ไวยากรณ์เล็กและเรียบง่าย เอกสารเป็น string, number, true, false, null, array หรือ object อย่างใดอย่างหนึ่ง string ล้อมด้วยเครื่องหมายคำพูดคู่และรองรับ escape เช่น \n, \t, \", \\, \uXXXX (Unicode code unit) number ไม่แยก integer กับ float และใช้ไวยากรณ์ทศนิยม object คือ ชุดที่ไม่เรียงลำดับ ของสมาชิกที่มี string key ส่วน array คือรายการที่เรียงลำดับ whitespace นอก string ไม่มีความหมาย
สิ่งที่ JSON จงใจไม่รวม ก็สำคัญ ไม่มี comment ไม่มี trailing comma ไม่อนุญาต string single-quote, hex หรือ binary literal อักขระนอก ASCII ต้องเป็น UTF-8 bytes ตรงๆ หรือ \u escape เท่านั้น JSON5 หรือ HJSON เป็น ฟอร์แมตคนละตัว ที่ผ่อนกฎบางข้อ parser JSON ที่เข้มงวดไม่ยอมรับ
การ parse สำเร็จหมายถึง “ถูก syntax” แต่ไม่ได้หมายถึง “ถูกต้องทางความหมาย” {"user": "x", "pass": "y"} เป็น JSON ที่ใช้ได้ แต่ถ้า endpoint คาดหวัง {"username": "...", "password": "..."} ในมุมของ application นี่คือข้อมูลผิด การจับปัญหานี้ต้องใช้ schema — ข้อกำหนดรูปร่างเอกสารที่เครื่องอ่านได้ — มาตรฐานนั้นคือ JSON Schema (meta-schema ที่แนะนำปัจจุบันคือ Draft 2020-12) รองรับ required field, type, enum·const, string pattern ด้วย regex, range ของ number, items·uniqueness ของ array, properties·additionalProperties ของ object, และการประกอบด้วย allOf·oneOf·anyOf·$ref Validator อย่าง Ajv 8.12 คอมไพล์ schema ครั้งเดียวเป็น function ให้ ค่าใช้จ่ายในการตรวจใน hot path จึงแทบไม่นับ — ใน API ที่ผมเคยดูแล การตรวจ Ajv หนึ่งครั้งอยู่ที่ไม่กี่สิบ microsecond โดยเฉลี่ย
Formatting ง่ายที่สุดใน 3 ขั้นตอน ไม่เปลี่ยนความหมาย แค่ปรับ whitespace และ indent JSON.stringify(obj, null, 2), jq . และ formatter ของ IDE ทำสิ่งนี้อยู่แล้ว เป็นเพียงเพื่อให้คนอ่าน ไม่มีความหมายกับเครื่อง
เปรียบเทียบและข้อมูล
| มุม | Formatting | Syntax validation | JSON Schema validation |
|---|---|---|---|
| วัตถุประสงค์ | จัด whitespace ให้อ่านง่าย | ตรวจว่า string parse เป็น JSON ได้ | ตรวจว่าค่าที่ parse แล้วมีรูปที่คาดหวัง |
| จับอะไรได้ | ไม่จับ (จัด whitespace) | วงเล็บไม่สมดุล เครื่องหมายคำพูดผิด escape ผิด trailing comma | missing required field, type ผิด, out-of-range, key ที่ไม่รู้จัก |
| จับอะไรไม่ได้ | ปัญหาโครงสร้าง·ความหมาย | รูปที่คาดหวัง·กฎธุรกิจ | กฎที่ไม่อยู่ใน schema, ความสัมพันธ์ระหว่าง field |
| เครื่องมือทั่วไป | JSON.stringify(obj, null, 2), jq, IDE formatter | JSON.parse, jq -e, parser แต่ละภาษา | Ajv 8.x, python-jsonschema, OpenAPI validator |
| ตำแหน่งที่ใช้ | เครื่องมือ dev·log | ทุก parsing boundary (โดยปริยาย) | API request/response, config load, message boundary |
3 คอลัมน์ไม่ใช่ “เลือก 1” แต่เป็น ชั้นเรียงตามลำดับ การเห็น package-lock.json 12MB ที่ติดเป็นบรรทัดเดียวแล้วแปลงเป็น indent ที่อ่านง่ายคือ formatting ทันทีที่เปลี่ยนชื่อเป็น “validation” เหตุจึงเริ่ม format เพื่ออ่าน parse เพื่อตรวจว่า string ไม่พัง ตรวจ schema เพื่อยืนยันรูปร่าง การใช้แค่สองขั้นตอนแรกและลืมขั้นสุดท้ายคือช่องโหว่ที่พบบ่อย
สถานการณ์จริง
สถานการณ์ 1 — debug API response payment gateway ภายนอกส่ง JSON 600 บรรทัดกลับมาเป็นบรรทัดเดียว Network panel ของ DevTools, local formatter หรือ pipe curl ... | jq . ครั้งเดียวก็พอจะแปลงเป็นรูปที่คนอ่านได้ ที่นี่ไม่มี validation เกิดขึ้น วัตถุประสงค์คือ “ดูด้วยตาว่าตรงไหนแปลก” และในขั้นตอนนี้สำคัญที่จะไม่พูดว่า “validate แล้ว”
สถานการณ์ 2 — โหลด config file สมมติว่าบริการอ่าน config.json ตอน boot parser JSON ที่เข้มงวดจับ trailing comma syntax error และบล็อก boot ซึ่งถูกต้อง แต่ตามกรณีเปิดบทความ หากตำแหน่งที่ควรเป็น retries: 3 มี retries: "three" อยู่ parse ผ่านปกติ และปัญหาปรากฏเมื่อโค้ดพยายามเปรียบเทียบ string กับ number ผมเคยแนะนำ pattern ให้ทีมหนึ่งดังนี้ — เรียก validator function ที่สร้างจาก Ajv compile(schema) ในบรรทัดแรกของ boot ตรวจ required·type·range·enum ถ้าละเมิด process.exit(1) การแก้ 5 นาทีนี้ป้องกันเหตุ staging สองครั้งใน 2 วันถัดมา
สถานการณ์ 3 — OpenAPI contract validation ทีมระบุ endpoint, request body, response shape ใน OpenAPI 3.1 ที่ components.schemas contract test ใช้ schema เดียวกัน validate payload ตัวอย่างจาก spec จับ drift ของ server implementation ที่คืน “integer แทน string” ก่อนที่ client จะพัง engine คือ JSON Schema validator เดียวกับการ validate config file เปลี่ยนเพียง scale ของการใช้งาน
ความเข้าใจผิดที่พบบ่อย
“JSON.parse อย่างเดียวก็พอ” เห็นแค่ syntax ไม่เห็นรูป parser ส่งคืน object ที่ว่างครึ่งหนึ่งอย่างยินดี วาง JSON.parse เป็น “กันแค่ string ที่พัง” แล้วเพิ่ม schema check อีกชั้นที่ trust boundary
“JSON Schema ใช้บนเซิร์ฟเวอร์เท่านั้น” validate ในเบราว์เซอร์ก่อนส่งช่วยให้ feedback ทันทีและลด load เซิร์ฟเวอร์ Ajv และ validator อื่นทำงานในเบราว์เซอร์ได้ดี server validation ยังจำเป็น — อย่าไว้ใจ client — แต่ client validation ช่วยปรับปรุง UX โดยไม่ทำให้ security model อ่อนลง
“JSON5 คือ JSON ที่ใส่ comment ได้” JSON5 เพิ่ม comment, key ที่ไม่ต้องมี quote, trailing comma, hex literal และอื่นๆ เป็น ฟอร์แมต config ที่เป็นมิตรต่อคน (ที่นิยมสุดคือ tsconfig.json ซึ่งจริงๆ เป็น JSONC อีก superset ที่ไม่ใช่มาตรฐาน) แต่ consumer ที่ยึดตาม RFC 8259 เข้มงวดไม่ยอมรับ ใช้ JSON5/JSONC เมื่อ consumer รองรับโดยระบุ ถ้าต้องส่งข้ามเครือข่ายหรือผ่าน parser ที่ไม่ใช่ของคุณให้ export เป็น strict JSON
“YAML คือ JSON ที่ใช้ indent” ทุก JSON ที่ใช้ได้เป็น YAML ที่ใช้ได้ แต่ YAML มี anchor·alias, tag type, multi-document, block·folded scalar, parsing ที่ไวต่อ indent และอื่นๆ ที่ JSON ไม่มี กับดักที่รู้จักกันคือ “Norwegian problem” YAML 1.1 parse NO ที่ไม่ใส่ quote เป็น boolean false การเปลี่ยน JSON เป็น YAML “เพราะอ่านง่ายขึ้น” เชิญปัญหาใหม่
เช็กลิสต์
- แค่อยากอ่านเท่านั้นใช่ไหม Format อย่างเดียว อย่าเรียกว่า “validate”
- ข้อมูลข้าม trust boundary หรือไม่ (HTTP request, message queue, config file) parse อย่างเดียวไม่พอ ต้องถึง JSON Schema
- ตัดสินใจเรื่อง field ที่ไม่รู้จักแล้วหรือยัง ตั้ง
additionalProperties: falseใน schema และตั้งนโยบายปฏิเสธ·ลบ - Error message นำไปใช้ได้หรือไม่ ตั้ง validator ให้แสดงการละเมิดทั้งหมดพร้อมกัน เช่น
allErrors: trueของ Ajv - Schema อยู่ใกล้โค้ดจริงไหม Drift ระหว่าง spec กับ implementation ห่างน้อยลงเมื่อ schema เป็นแหล่งความจริงเดียวของทั้งสองฝั่ง
- ฟอร์แมตเหมาะกับ JSON ไหม ถ้าเป็น config ที่คนแก้ไขและเลือก parser ได้เอง JSON5/JSONC·TOML ใจดีกว่า ถ้าเป็นการแลกเปลี่ยนระหว่างเครื่อง ให้ใช้ strict JSON
เครื่องมือที่เกี่ยวข้อง
JSON Formatter ของ Patrache Studio ทำงานในเบราว์เซอร์ payload ที่ paste ไม่ออกจากเครื่อง — มีประโยชน์เมื่อดู production response ที่มีข้อมูลส่วนตัว JSON payload ไม่ได้เดินทางคนเดียว ถ้ามี binary ที่ encode แล้วอยู่ข้างใน Base64·URL Encoding อธิบาย ครอบคลุมว่าทำไมขนาดจึงเพิ่มและเมื่อใดควรใช้วิธีส่งอื่น ถ้ามี ID ดู เปรียบเทียบ UUID v1·v4·v7 และการออกแบบ Primary Key ของฐานข้อมูล ว่าทำไม version ที่เซิร์ฟเวอร์ออกจึงกระทบ index·sort·cache ของระบบปลายทาง
อ้างอิง
- IETF RFC 8259, “The JavaScript Object Notation (JSON) Data Interchange Format” — https://datatracker.ietf.org/doc/html/rfc8259
- JSON Schema specification (Draft 2020-12) — https://json-schema.org/specification
- Ajv — Another JSON Schema Validator — https://ajv.js.org/
- MDN, “Working with JSON” — https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JSON