Không đóng additionalProperties thì xác thực Schema vô nghĩa
Kết luận: Xác thực thành công không đồng nghĩa dữ liệu sạch. Pydantic mặc định bỏ qua trường lạ, Zod gỡ bỏ chúng, Ajv cho qua — tham số bịa và khóa gõ sai lọt thẳng. Bài viết chỉ ra ba dạng chấp nhận im lặng và đưa ra cách làm đầy đủ: đóng đối tượng, kiểm soát ép kiểu, siết từng tầng lồng nhau.

Kết luận: mặc định, các trình xác thực bỏ qua trường chưa khai báo thay vì từ chối. Không có additionalProperties: false, một lần xác thực thành công chỉ chứng minh các trường đã khai báo ổn.
Hành vi nguy hiểm nhất của trình xác thực không phải là báo lỗi, mà là im lặng. Một khóa gõ sai hay tham số mô hình bịa ra đi xuyên qua xác thực không tiếng động và chỉ nổ ở tầng nghiệp vụ.
Một: lần xác thực "đạt"
{
"type": "object",
"properties": {
"order_id": { "type": "string" },
"amount": { "type": "number" }
},
"required": ["order_id", "amount"]
}
Gửi {"order_id": "A1", "amount": 50, "reasn_code": "damaged"} — khóa thứ ba là lỗi gõ. Với cấu hình mặc định của Ajv, kết quả là đạt. Trường thừa không bị từ chối cũng không được đánh dấu.
Trong đặc tả JSON Schema, giá trị mặc định của additionalProperties là true: trường chưa khai báo được phép xuất hiện.
Hai: ba dạng của sự im lặng
| Môi trường | Hành vi mặc định | Hệ quả |
|---|---|---|
| Ajv (JS) | cho qua | dữ liệu bẩn vào tới logic |
Zod z.object |
gỡ bỏ | trường biến mất, đối tượng ngắn lại |
| Pydantic v2 | bỏ qua (extra="ignore") |
tương tự, không dấu vết trong log |
Gỡ bỏ kín đáo hơn cho qua. Cả hai thư viện mặc định thất bại theo cùng một cách — im lặng.
Ba: gọi công cụ LLM khuếch đại vấn đề
Tham số do mô hình sinh ra: nó tự bịa tên trường và truyền "3" thay cho số. Các nền tảng định nghĩa tham số bằng JSON Schema nhưng mặc định không ép schema phải đóng — chế độ strict phải bật tường minh, và ràng buộc ngữ pháp chỉ áp cho đầu ra trực tiếp của mô hình.
{
"order_id": 4411,
"refund_amount_usd": 50,
"reason_code": "damaged",
"urgent": "yes"
}
Khi đóng đối tượng, reason_code và urgent bị từ chối và lỗi trỏ tới đường dẫn /reason_code.
Bốn: cách đóng
Ajv: thêm additionalProperties: false tại nút đối tượng.
import Ajv from "ajv";
const ajv = new Ajv({ allErrors: true });
const schema = {
type: "object",
properties: { order_id: { type: "string" }, amount: { type: "number" } },
required: ["order_id", "amount"],
additionalProperties: false,
};
const validate = ajv.compile(schema);
validate({ order_id: "A1", amount: 50, reasn_code: "x" }); // false
Zod: đổi z.object thành z.strictObject hoặc nối .strict(). Pydantic: cấu hình ConfigDict(extra="forbid").
Quy tắc chung: đóng theo từng tầng. Mỗi đối tượng lồng nhau cần additionalProperties: false riêng.
Năm: chính sách ép kiểu
Chế độ lax của Pydantic biến "5" thành 5. Ba chính sách: nghiêm ngặt (từ chối sai kiểu), kiểm soát (chỉ chuyển đổi tường minh có giới hạn), thả lỏng (chỉ dữ liệu nội bộ). Với đầu vào không đáng tin, chọn 1 hoặc 2 và ghi rõ vào kiểm thử.
Sáu: danh mục kiểm tra tầng lồng
| Điểm | Việc |
|---|---|
| Đối tượng gốc | additionalProperties: false |
| Mỗi đối tượng lồng | như vậy |
| Phần tử mảng | đóng cả đối tượng trong items |
| Enum và khoảng | thêm enum, minimum/maximum |
| Schema sinh tự động | bổ sung thủ công |
Bảy: sau khi từ chối
allErrors: true báo hết các trường vi phạm một lần; verbose: true cho đường dẫn. Lỗi cú pháp nên được parser chặn trước khi xác thực schema.
Câu hỏi thường gặp
Quá nghiêm ngặt?
Tùy hợp đồng. Với đầu vào do bạn định nghĩa thì nên đóng; với dữ liệu bên thứ ba có phần mở rộng, chỉ đóng lõi và tách vùng mở rộng.
Strip của Zod an toàn hơn chứ?
Không: nó bảo vệ hạ nguồn bằng cái giá của bên gọi. Lỗi cần xuất hiện sớm và rõ ràng.
Chế độ strict của nền tảng thay thế xác thực cục bộ?
Không. Bản thân schema có thể đang mở, và ngữ pháp có thể không phủ tham số công cụ.
Sau khi đóng, dữ liệu cũ lỗi hàng loạt?
Đó chính là điều cần lộ ra. Kiểm kê bằng allErrors, thêm các trường vô hại, sửa các trường có hại.
Giải pháp một dòng?
Không, nhưng thao tác tối thiểu: kiểm tra mỗi type: "object" có kèm additionalProperties: false.