สร้าง TypeScript type จาก Schema
สรุปสำคัญ: json-schema-to-typescript และ quicktype สร้าง .d.ts ได้ในคำสั่งเดียว แต่มันเป็นเพียงร่าง: optionality, union, additionalProperties และวันที่ยังต้องมีคนตรวจ

สรุปสั้น ๆ: สร้างด้วย json-schema-to-typescript หรือ quicktype แล้วแก้ด้วยมือเรื่อง optionality, union, additionalProperties และวันที่; ให้ Schema เป็นต้นทางและ type เป็นผลลัพธ์ โดยบังคับด้วยขั้นตอน types:check ใน CI
คำสั่งเดียวได้ไฟล์ แต่ไม่ได้ type ที่ถูกต้อง
คำสั่งบรรทัดเดียว
npx json-schema-to-typescript schema.json > src/types/order.d.ts
มันจัดการ $ref, allOf, oneOf, anyOf และแปลง description เป็น JSDoc สำหรับการสร้างเป็นชุดพร้อม banner และ style ที่เลือกได้ ให้เรียก Node API ของมันจาก build-types.mjs เล็ก ๆ
quicktype คือทางเลือก และยังอนุมาน schema จาก JSON ตัวอย่างได้ด้วย:
npx quicktype -s schema schema.json -o order.ts --lang typescript
สี่จุดที่ต้องแก้ทีหลัง
Optionality อะไรที่ไม่อยู่ใน required จะกลายเป็น ? แต่ "มีเสมอ อาจเป็น null" กับ "อาจไม่มี" เป็นสัญญาที่ต่างกัน:
interface Order {
remark?: string | null; // อาจไม่มีก็ได้
status: string;
}
ถ้า backend ส่ง remark: null เสมอ ให้ตัด ? ออกและเลิกให้ผู้ใช้ทุกคนต้องป้องกัน undefined
Union ที่ไม่มี discriminant oneOf มักกลายเป็น A | B ซึ่ง TypeScript narrow ไม่ได้หากไม่มีฟิลด์ literal ให้เพิ่มพร็อพเพอร์ตี้ type ที่ค่าคงที่ในแต่ละสาขาของ Schema
additionalProperties เมื่อละไว้หรือเป็น true บางครั้งให้ [k: string]: unknown บางครั้งให้ any แบบแรกปลอดภัย แบบหลังปิดการตรวจชนิดของ object ทั้งก้อนอย่างเงียบ ๆ
รูปแบบวันที่ format: "date-time" คือสตริงบน wire การแมปเป็น Date หมายความว่าต้อง deserialize ด้วย มิฉะนั้นค่าตอนรันไทม์ยังเป็นสตริงขณะที่ type โกหก
ให้ทิศทางคงที่
Schema คือต้นทาง type คือผลลัพธ์ การสร้าง Schema จาก type ที่เขียนด้วยมือยอมรับได้เฉพาะเมื่อการสร้างใหม่เป็นภาคบังคับและถูกบังคับใน CI — มิฉะนั้นทั้งสองฝั่งจะคลาดกันและไม่มีใครรู้ว่าฝั่งไหนเป็นหลัก
ใส่ไว้ใน pipeline
"types:gen": "json2ts -i schemas/ -o src/types/generated",
"types:check": "pnpm types:gen && git diff --exit-code src/types/generated"
types:check คือส่วนที่สำคัญ: แก้ Schema โดยไม่สร้างใหม่แล้ว CI ล้มเหลว
คำถามที่พบบ่อย
ทำไมไฟล์ที่สร้างขึ้นมี any?
มักมาจาก additionalProperties ที่เป็น true หรือถูกละไว้ ซึ่ง generator บางตัวแมปเป็น [k: string]: any ให้แทนด้วย unknown หรือฟิลด์ที่ระบุชัด เพราะ any ปิดการตรวจชนิดของ object ทั้งก้อน
ควรแก้ฟิลด์ optional หลังสร้างอย่างไร?
อะไรที่ไม่อยู่ใน required จะกลายเป็น ? แต่ "มีเสมอ อาจเป็น null" ต่างจาก "อาจไม่มี" ถ้า backend ส่งฟิลด์นี้เสมอ ให้ตัด ? ออก
CI ควรล้มเหลวเมื่อ Schema กับ type คลาดกันไหม?
ควร สคริปต์ types:check ที่สร้างใหม่แล้วรัน git diff --exit-code จะทำให้ build ล้มเหลวทันทีที่ Schema เปลี่ยนโดยไม่สร้างใหม่