使用Ajv与TypeScript编写验证器时遇类型不兼容错误求助
问题代码
import Ajv, { JSONSchemaType } from "ajv"; const ajv = new Ajv(); interface Attribute { name: string; type: string; ref?: boolean; cardinality?: string; optional?: boolean; } interface Concept { type: string; root?: boolean; attributes?: Array<Attribute>; hidden?: boolean; terminal?: boolean; expression?: string; } const properties = { name: { type: "string" }, type: { type: "string" }, ref: { type: "boolean" }, cardinality: { type: "string"}, optional: { type: "boolean" }, }; const conceptSchema: JSONSchemaType<Concept> = { type: "object", properties: { type: { type: "string" }, root: { type: "boolean"}, attributes: { type: "array", uniqueItems: true, items: { type: "object", properties: properties, required: ["name", "type"], }, }, hidden: { type: "boolean" }, terminal: { type: "boolean" }, expression: { type: "string" }, }, required: ["type"], additionalProperties: false, };
错误信息
"message": "类型 '{ type: "object"; properties: { type: { type: "string"; }; root: { type: "boolean"; }; attributes: { type: "array"; uniqueItems: true; items: { type: "object"; properties: { name: { type: string; }; type: { type: string; }; ref: { ...; }; cardinality: { ...; }; optional: { ...; }; }; required: ("type" | "name")[]; }...' 无法分配给类型 'UncheckedJSONSchemaType<Concept, false>'。
'properties.attributes' 的类型在两种类型中不兼容。
类型 '{ type: "array"; uniqueItems: true; items: { type: "object"; properties: { name: { type: string; }; type: { type: string; }; ref: { type: string; }; cardinality: { type: string; pattern: string; }; optional: { type: string; }; }; required: ("type" | "name")[]; }; }' 无法分配给类型 '{ $ref: string; } | (UncheckedJSONSchemaType<Attribute[] | undefined, false> & { nullable: true; const?: null | undefined; enum?: readonly (Attribute[] | null | undefined)[] | undefined; default?: Attribute[] | ... 1 more ... | undefined; })'。
'items' 属性的类型不兼容。
类型 '{ type: "object"; properties: { name: { type: string; }; type: { type: string; }; ref: { type: string; }; cardinality: { type: string; pattern: string; }; optional: { type: string; }; }; required: ("type" | "name")[]; }' 无法分配给类型 'UncheckedJSONSchemaType<Attribute, false>'。
'properties.name' 的类型在两种类型中不兼容。
类型 '{ type: string; }' 无法分配给类型 '{ $ref: string; } | (UncheckedJSONSchemaType<string, false> & { const?: string | undefined; enum?: readonly string[] | undefined; default?: string | undefined; })'。
类型 '{ type: string; }' 无法分配给类型 '{ type: "string"; } & StringKeywords & { allOf?: readonly UncheckedPartialSchema[] | undefined; anyOf?: readonly UncheckedPartialSchema[] | undefined; ... 4 more ...; not?: UncheckedPartialSchema<...> | undefined; } & { ...; } & { ...; }'。
类型 '{ type: string; }' 无法分配给类型 '{ type: "string"; }'。
'type' 属性的类型不兼容。
类型 'string' 无法分配给类型 '"string"'。"
问题原因
错误的核心是TypeScript的类型推断机制:
- 单独定义的
properties对象中,{ type: "string" }里的"string"会被TypeScript默认推断为通用的string类型,而非精确的字面量类型"string"。 - Ajv的
JSONSchemaType要求JSON Schema中的type字段必须是字面量类型(比如"string"、"boolean"),以此和TypeScript接口的类型严格对应,所以出现类型不匹配。
解决方法
方法1:用as const锁定字面量类型
给properties中每个type的值添加as const断言,强制TypeScript将其识别为字面量类型:
const properties = { name: { type: "string" as const }, type: { type: "string" as const }, ref: { type: "boolean" as const }, cardinality: { type: "string" as const }, optional: { type: "boolean" as const }, };
方法2:给properties指定精确类型
直接为properties定义符合Ajv要求的类型,明确每个属性的type是字面量类型:
import { JSONSchemaType } from "ajv"; type AttributePropertySchema = { name: JSONSchemaType<string>; type: JSONSchemaType<string>; ref: JSONSchemaType<boolean>; cardinality: JSONSchemaType<string>; optional: JSONSchemaType<boolean>; }; const properties: AttributePropertySchema = { name: { type: "string" }, type: { type: "string" }, ref: { type: "boolean" }, cardinality: { type: "string"}, optional: { type: "boolean" }, };
方法3:内联properties定义
如果不需要复用properties,直接把属性定义写在conceptSchema的items中,TypeScript会自动推断字面量类型:
const conceptSchema: JSONSchemaType<Concept> = { type: "object", properties: { type: { type: "string" }, root: { type: "boolean"}, attributes: { type: "array", uniqueItems: true, items: { type: "object", properties: { name: { type: "string" }, type: { type: "string" }, ref: { type: "boolean" }, cardinality: { type: "string"}, optional: { type: "boolean" }, }, required: ["name", "type"], }, }, hidden: { type: "boolean" }, terminal: { type: "boolean" }, expression: { type: "string" }, }, required: ["type"], additionalProperties: false, };
内容的提问来源于stack exchange,提问作者Humberto

