本文目录导读:

在编程中(尤其是JSON Schema、TypeScript、Zod、OpenAPI等场景),anyOf 是一个组合类型(Composition)关键字,用于定义“满足多个条件中的任意一个即可”。
下面我通过最典型的 JSON Schema 和 TypeScript 来展示 anyOf 的实战案例和底层逻辑。
场景 1:JSON Schema 中的 anyOf(API 数据校验)
假设你正在设计一个支付系统 API,用户提交的支付方式必须是 “信用卡” 或 “支付宝” 之一,但两者的数据结构完全不同。
基础版(宽松校验)
{
"$schema": "http://json-schema.org/draft-07/schema#",: "Payment Method",
"type": "object",
"anyOf": [
{
"properties": {
"cardNumber": { "type": "string", "pattern": "^[0-9]{16}$" },
"cvv": { "type": "string", "pattern": "^[0-9]{3}$" }
},
"required": ["cardNumber", "cvv"],
"additionalProperties": false // 禁止额外属性
},
{
"properties": {
"alipayAccount": { "type": "string", "format": "email" },
"oauthToken": { "type": "string" }
},
"required": ["alipayAccount", "oauthToken"],
"additionalProperties": false
}
]
}
测试用例
- ✅ 合法:
{ "cardNumber": "1234567812345678", "cvv": "123" } - ✅ 合法:
{ "alipayAccount": "user@xx.com", "oauthToken": "abc123" } - ❌ 非法:
{ "cardNumber": "1234", "cvv": "12" }(两个分支都失败) - ❌ 非法:
{ "cardNumber": "1234567812345678", "cvv": "123", "alipayAccount": "x@y.com" }(虽然第一个分支成功了,但第二个分支因缺少oauthToken失败。注意:anyOf 只要有一个成功就通过校验,这里第一个分支成功,所以整体是合法的,除非你额外加了maxProperties限制)。
核心要点:
anyOf只要至少一个分支通过校验,整体即通过,它和oneOf的区别是:oneOf要求恰好一个(用于区分互斥场景)。
场景 2:TypeScript 中的等效实现( 联合类型 + 类型守卫)
TypeScript 原生使用 运算符,但在运行时校验(比如接收外部数据)时,我们需要自定义类型守卫来模拟 anyOf 逻辑。
定义类型(相当于 JSON Schema 的两个分支)
type CreditCard = {
cardNumber: string;
cvv: string;
};
type Alipay = {
alipayAccount: string;
oauthToken: string;
};
// 联合类型 = anyOf
type PaymentMethod = CreditCard | Alipay;
运行时校验(纯手写实现 anyOf)
function isCreditCard(obj: any): obj is CreditCard {
return (
typeof obj.cardNumber === 'string' &&
/^[0-9]{16}$/.test(obj.cardNumber) &&
typeof obj.cvv === 'string' &&
/^[0-9]{3}$/.test(obj.cvv)
);
}
function isAlipay(obj: any): obj is Alipay {
return (
typeof obj.alipayAccount === 'string' &&
obj.alipayAccount.includes('@') &&
typeof obj.oauthToken === 'string'
);
}
// anyOf 的实现:只要有一个为真即为真
function isPaymentMethod(obj: any): obj is PaymentMethod {
return isCreditCard(obj) || isAlipay(obj);
}
实际使用
const rawData = JSON.parse('{"cardNumber":"1234567812345678","cvv":"123"}');
if (isPaymentMethod(rawData)) {
// TypeScript 自动收窄类型为 CreditCard | Alipay
if ('cardNumber' in rawData) {
console.log('这是信用卡', rawData.cvv);
} else {
console.log('这是支付宝', rawData.oauthToken);
}
} else {
console.error('不满足任何支付方式');
}
场景 3:使用 Zod(类库优雅实现)
Zod 内置了 .or() 方法,直接映射 JSON Schema 的 anyOf。
import { z } from 'zod';
// 定义两个 schema
const CardSchema = z.object({
cardNumber: z.string().regex(/^[0-9]{16}$/),
cvv: z.string().regex(/^[0-9]{3}$/),
});
const AlipaySchema = z.object({
alipayAccount: z.string().email(),
oauthToken: z.string(),
});
// anyOf 实现
const PaymentSchema = CardSchema.or(AlipaySchema); // 或者 z.union([CardSchema, AlipaySchema])
// 使用
const result = PaymentSchema.safeParse({ cardNumber: '...', ... });
if (result.success) {
console.log('校验通过,这是其中的一种支付方式');
} else {
console.log('两种都不是');
}
易混淆点对比
| 关键字/运算符 | 要求 | 典型使用场景 |
|---|---|---|
anyOf (或 ) |
至少满足一个(允许多个同时满足) | 只要数据属于任一合法形态即可,不关心冲突 |
oneOf |
恰好满足一个(多个满足则报错) | 多态数据,必须明确区分是哪一种类型(如动物分类) |
allOf (或 &) |
必须全部满足 | 组合复用多个基础 schema 的公共字段 |
anyOf 的核心逻辑就是“或”运算(OR)。 在实际开发中,它很好地解决了异步数据格式混合(比如一个接口根据 type 字段返回不同的结构)的校验问题。
你可以根据自己的技术栈选择:
- 原生 JS: 手写
if (isA || isB)。 - TS / Zod: 使用
union组合(类型层面更安全)。