如何用Superstruct优雅验证包含固定字段与动态字段的对象?
如何用Superstruct优雅验证包含固定字段与动态字段的对象?
你的问题核心在于:Superstruct的record会对所有字段应用结构约束,所以当你用intersection把固定字段结构和record结构合并时,固定字段(thisFieldIsAlwaysHere是number类型)会不符合record要求的{ body: number }结构,导致验证失败。
这里有一个非常优雅的解决方案:通过给record的键结构添加过滤规则,排除固定字段的名称,让record只对非固定字段生效,再和固定字段结构做intersection。
具体实现步骤
- 定义一个排除特定键的字符串结构:用
refine包装string(),确保键不是固定字段的名称。 - 用这个自定义键结构创建
record,再和固定字段的结构做intersection。
完整代码示例
import { type, intersection, record, refine, number, string, Infer, validate } from 'superstruct'; // 1. 定义排除固定字段的字符串结构 const nonFixedKey = refine(string(), (value) => { // 排除固定字段的名称,支持多个字段(比如换成!['field1', 'field2'].includes(value)) return value !== 'thisFieldIsAlwaysHere'; }, { message: '键不能是"thisFieldIsAlwaysHere"' }); // 2. 构建父结构:固定字段 + 过滤后的动态字段 const ParentStruct = intersection([ type({ thisFieldIsAlwaysHere: number() }), record(nonFixedKey, type({ body: number() })) ]); // 3. 最终的验证结构 const Struct = type({ parent: ParentStruct }); // TypeScript类型推导 type MyObjectType = Infer<typeof Struct>; // 类型为:{ parent: { thisFieldIsAlwaysHere: number } & Record<Exclude<string, 'thisFieldIsAlwaysHere'>, { body: number }> }
验证效果测试
对于你的示例对象:
const validObj = { parent: { thisFieldIsAlwaysHere: 123, arbitraryObject1: { body: 456 }, arbitraryObject2: { body: 789 }, } }; const [error, result] = validate(validObj, Struct); console.log(error); // null,验证通过 // 测试非法情况:动态字段不符合结构 const invalidObj = { parent: { thisFieldIsAlwaysHere: 123, arbitraryObject1: 456, // 不是{ body: number }结构 } }; const [invalidError] = validate(invalidObj, Struct); console.log(invalidError?.message); // 验证失败,提示字段不符合结构
为什么这个方案优雅?
- 完全利用Superstruct的原生API组合,没有自定义复杂的验证逻辑。
- TypeScript类型推导完全正确:自动推断出
parent是固定字段 + 排除固定键的Record类型。 - 扩展性强:如果以后要添加更多固定字段,只需要修改
refine里的过滤条件即可。
其他备选方案
如果你的场景更复杂(比如固定字段数量很多,或者动态字段的规则需要动态生成),也可以用dynamic自定义验证逻辑,但类型推导需要手动调整,不如第一个方案优雅:
const ParentStruct = dynamic(() => type({ thisFieldIsAlwaysHere: number() }), (value, context) => { // 先验证固定字段 const fixedStruct = type({ thisFieldIsAlwaysHere: number() }); const fixedResult = validate(value, fixedStruct, context); if (fixedResult.error) return fixedResult.error; // 验证所有非固定字段 const dynamicFields = Object.fromEntries( Object.entries(value).filter(([key]) => key !== 'thisFieldIsAlwaysHere') ); const dynamicStruct = record(string(), type({ body: number() })); return validate(dynamicFields, dynamicStruct, context).error; });
备注:内容来源于stack exchange,提问作者user16717440
相关产品推荐
相关产品推荐

