ขนมปังกลมต้นฉบับกำลังเปิดกล่องไม้ซ้อนชั้นสองใบและเทียบช่องด้านในทีละชั้น

Bun.deepEquals: เทียบ config ซ้อนชั้นโดยรู้ว่า strict ต่างตรงไหน

พี่โชว์Engineering

การเทียบ config ด้วย JSON.stringify(a) === JSON.stringify(b) ผูกกับลำดับ key และทำข้อมูลบางชนิดหายครับ Bun.deepEquals เดินโครงสร้างให้และเป็นกลไกเดียวกับ expect().toEqual() ใน test runner ของ Bun จึงเหมาะกับ snapshot ในหน่วยความจำที่ไม่อยาก serialize ก่อน

ให้นึกถึงกล่องเครื่องมือสองใบ เราไม่ได้ชั่งแค่น้ำหนักรวม แต่เปิดลิ้นชักซ้อนกันแล้วเทียบชิ้นส่วนข้างใน ส่วน strict mode คือการตรวจด้วยว่าช่องว่างจริง ๆ ต่างจากช่องที่ใส่ป้าย “ไม่มีค่า”

const current = {
  retries: 3,
  route: { region: "apac", enabled: true },
  tags: ["daily", "verified"],
};

const candidate = structuredClone(current);
if (!Bun.deepEquals(current, candidate)) {
  throw new Error("equal snapshots should match");
}

if (!Bun.deepEquals({}, { optional: undefined })) {
  throw new Error("non-strict mode should ignore undefined property");
}
if (Bun.deepEquals({}, { optional: undefined }, true)) {
  throw new Error("strict mode should preserve object shape");
}

console.log("Bun.deepEquals checks passed");

เลือก strict ตามสัญญา

non-strict mode เหมาะกับ config ที่ “ไม่มี key” และ “มี key ค่า undefined” ถือว่าเหมือนกัน แต่ schema validator, migration หรือ cache key อาจต้องรู้ว่าผู้ใช้ส่ง field มาหรือไม่ กรณีนั้นใช้ argument ที่สามเป็น true เอกสารยังยกกรณี sparse array และ object literal เทียบกับ class instance ซึ่ง strict mode แยกให้

ฟังก์ชันนี้ตอบความเท่ากัน ไม่ได้ validate schema และไม่สร้าง diff ที่บอกตำแหน่งผิด หากต้องอธิบาย error ให้ผู้ใช้ ควร validate ทีละ field หรือใช้ test assertion ที่แสดงรายละเอียด อย่าใช้ object ที่แก้ไปพร้อมกันหลาย thread/worker ระหว่างเทียบ และอย่านำเวลารันครั้งเดียวไปอ้าง performance โดยไม่ benchmark กับรูปร่างข้อมูลจริง

Recap

  • ใช้ deep equality แทน stringify เมื่อ key order ไม่ใช่ความหมาย
  • เปิด strict เมื่อ shape, prototype หรือ sparse slot สำคัญ
  • แยก equality ออกจาก schema validation
  • ใช้ assertion/diff tool เมื่อผู้ใช้ต้องรู้ว่าต่างตรงไหน