198 lines
4.3 KiB
Markdown
198 lines
4.3 KiB
Markdown
# JSON Schema 类型推断优化说明
|
||
|
||
## 问题描述
|
||
|
||
之前在使用 `createQueryApi` 时,如果 API 定义使用 JSON Schema(而不是 Zod schema),参数类型会被推断为 `unknown`,导致失去类型安全性。
|
||
|
||
## 优化方案
|
||
|
||
### 修改内容
|
||
|
||
在 `src/query-api.ts` 中增强了 `InferFromJSONSchema` 和 `InferType` 类型,使其能够正确处理:
|
||
|
||
1. **嵌套对象的 JSON Schema**
|
||
2. **带有 `$schema` 字段的 JSON Schema**
|
||
3. **没有 `type` 字段但有 `properties` 字段的对象**
|
||
|
||
### 核心改进
|
||
|
||
```typescript
|
||
// 增强的 InferFromJSONSchema 类型
|
||
type InferFromJSONSchema<T> =
|
||
// ... 基础类型处理 ...
|
||
// 新增:处理没有 type 但有 properties 的对象
|
||
T extends Record<string, any>
|
||
? T extends { properties: infer P }
|
||
? { [K in keyof P]: InferFromJSONSchema<P[K]> }
|
||
: unknown
|
||
: unknown;
|
||
|
||
// 增强的 InferType 类型
|
||
type InferType<T> =
|
||
T extends z.ZodType<infer U> ? U :
|
||
T extends { type: infer TType } ? InferFromJSONSchema<T> :
|
||
T extends { properties: infer P } ? InferFromJSONSchema<T> : // 新增
|
||
T;
|
||
```
|
||
|
||
## 使用示例
|
||
|
||
### 示例 1: 基础 JSON Schema
|
||
|
||
```typescript
|
||
const api = {
|
||
"user": {
|
||
"get": {
|
||
"path": "user",
|
||
"key": "get",
|
||
"metadata": {
|
||
"args": {
|
||
"userId": { "type": "string" },
|
||
"includeProfile": { "type": "boolean" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} as const;
|
||
|
||
const queryApi = createQueryApi({ api });
|
||
|
||
// ✅ 完整的类型推断
|
||
queryApi.user.get({
|
||
userId: '123', // string
|
||
includeProfile: true // boolean
|
||
});
|
||
```
|
||
|
||
### 示例 2: 嵌套对象(你的场景)
|
||
|
||
```typescript
|
||
const api = {
|
||
"app_domain_manager": {
|
||
"get": {
|
||
"path": "app_domain_manager",
|
||
"key": "get",
|
||
"metadata": {
|
||
"args": {
|
||
"data": {
|
||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||
"type": "object",
|
||
"properties": {
|
||
"id": { "type": "string" },
|
||
"domain": { "type": "string" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} as const;
|
||
|
||
const queryApi = createQueryApi({ api });
|
||
|
||
// ✅ data 参数被正确推断为 { id: string, domain: string }
|
||
queryApi.app_domain_manager.get({
|
||
data: {
|
||
id: '123',
|
||
domain: 'example.com'
|
||
}
|
||
});
|
||
```
|
||
|
||
### 示例 3: 枚举类型
|
||
|
||
```typescript
|
||
const api = {
|
||
"order": {
|
||
"updateStatus": {
|
||
"metadata": {
|
||
"args": {
|
||
"status": {
|
||
"type": "string",
|
||
"enum": ["pending", "processing", "shipped"] as const
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} as const;
|
||
|
||
const queryApi = createQueryApi({ api });
|
||
|
||
// ✅ status 被推断为 "pending" | "processing" | "shipped"
|
||
queryApi.order.updateStatus({
|
||
status: 'shipped' // 自动补全和类型检查
|
||
});
|
||
```
|
||
|
||
### 示例 4: 复杂嵌套
|
||
|
||
```typescript
|
||
const api = {
|
||
"product": {
|
||
"create": {
|
||
"metadata": {
|
||
"args": {
|
||
"product": {
|
||
"type": "object",
|
||
"properties": {
|
||
"name": { "type": "string" },
|
||
"price": { "type": "number" },
|
||
"tags": {
|
||
"type": "array",
|
||
"items": { "type": "string" }
|
||
},
|
||
"metadata": {
|
||
"type": "object",
|
||
"properties": {
|
||
"color": { "type": "string" }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} as const;
|
||
|
||
const queryApi = createQueryApi({ api });
|
||
|
||
// ✅ 深度嵌套的类型推断
|
||
queryApi.product.create({
|
||
product: {
|
||
name: 'T-Shirt', // string
|
||
price: 29.99, // number
|
||
tags: ['summer'], // string[]
|
||
metadata: {
|
||
color: 'blue' // string
|
||
}
|
||
}
|
||
});
|
||
```
|
||
|
||
## 支持的 JSON Schema 特性
|
||
|
||
- ✅ 基础类型: `string`, `number`, `integer`, `boolean`
|
||
- ✅ 对象类型: `type: "object"` with `properties`
|
||
- ✅ 数组类型: `type: "array"` with `items`
|
||
- ✅ 枚举类型: `enum` 字段
|
||
- ✅ 嵌套对象和数组
|
||
- ✅ 忽略 `$schema` 等元数据字段
|
||
- ✅ 支持 `as const` 断言以获得更精确的类型
|
||
|
||
## 类型安全
|
||
|
||
优化后,TypeScript 会:
|
||
- ✅ 提供完整的自动补全
|
||
- ✅ 在编译时检测类型错误
|
||
- ✅ 防止传入不存在的属性
|
||
- ✅ 确保值的类型正确
|
||
|
||
## 测试
|
||
|
||
运行测试文件验证类型推断:
|
||
```bash
|
||
bun test/json-schema-examples.ts
|
||
```
|