New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Strict by default or strict deep #2062
Comments
I also think it would be worthwhile to have this in the API (and possibly If you're in a hurry, I think you can implement it yourself. Take a look at deepPartial(): partialUtil.DeepPartial<this> {
return deepPartialify(this) as any;
} function deepPartialify(schema: ZodTypeAny): any {
if (schema instanceof ZodObject) {
const newShape: any = {};
for (const key in schema.shape) {
const fieldSchema = schema.shape[key];
newShape[key] = ZodOptional.create(deepPartialify(fieldSchema));
}
return new ZodObject({
...schema._def,
shape: () => newShape,
}) as any;
} else if (schema instanceof ZodArray) {
return ZodArray.create(deepPartialify(schema.element));
} else if (schema instanceof ZodOptional) {
return ZodOptional.create(deepPartialify(schema.unwrap()));
} else if (schema instanceof ZodNullable) {
return ZodNullable.create(deepPartialify(schema.unwrap()));
} else if (schema instanceof ZodTuple) {
return ZodTuple.create(
schema.items.map((item: any) => deepPartialify(item))
);
} else {
return schema;
}
} |
I've tried to generalize to all kind of deep unknown policies. Here's what I got: First, you need 2 utilities: a function and a type. type ZodObjectMapper<T extends ZodRawShape, U extends UnknownKeysParam> = (
o: ZodObject<T>
) => ZodObject<T, U>;
function deepApplyObject(
schema: ZodTypeAny,
map: ZodObjectMapper<any, any>
): any {
if (schema instanceof ZodObject) {
const newShape: Record<string, ZodTypeAny> = {};
for (const key in schema.shape) {
const fieldSchema = schema.shape[key];
newShape[key] = deepApplyObject(fieldSchema, map);
}
const newObject = new ZodObject({
...schema._def,
shape: () => newShape,
});
return map(newObject);
} else if (schema instanceof ZodArray) {
return ZodArray.create(deepApplyObject(schema.element, map));
} else if (schema instanceof ZodOptional) {
return ZodOptional.create(deepApplyObject(schema.unwrap(), map));
} else if (schema instanceof ZodNullable) {
return ZodNullable.create(deepApplyObject(schema.unwrap(), map));
} else if (schema instanceof ZodTuple) {
return ZodTuple.create(
schema.items.map((item: any) => deepApplyObject(item, map))
);
} else {
return schema;
}
} type DeepUnknownKeys<
T extends ZodTypeAny,
UnknownKeys extends UnknownKeysParam
> = T extends ZodObject<infer Shape, infer _, infer Catchall>
? ZodObject<
{
[k in keyof Shape]: DeepUnknownKeys<Shape[k], UnknownKeys>;
},
UnknownKeys,
Catchall
>
: T extends ZodArray<infer Type, infer Card>
? ZodArray<DeepUnknownKeys<Type, UnknownKeys>, Card>
: T extends ZodOptional<infer Type>
? ZodOptional<DeepUnknownKeys<Type, UnknownKeys>>
: T extends ZodNullable<infer Type>
? ZodNullable<DeepUnknownKeys<Type, UnknownKeys>>
: T extends ZodTuple<infer Items>
? {
[k in keyof Items]: Items[k] extends ZodTypeAny
? DeepUnknownKeys<Items[k], UnknownKeys>
: never;
} extends infer PI
? PI extends ZodTupleItems
? ZodTuple<PI>
: never
: never
: T; Then we only need to define those 3 functions + types: type DeepPassthrough<T extends ZodTypeAny> = DeepUnknownKeys<T, 'passthrough'>;
function deepPassthrough<T extends ZodTypeAny>(schema: T): DeepPassthrough<T> {
return deepApplyObject(schema, (s) => s.passthrough()) as DeepPassthrough<T>;
}
type DeepStrip<T extends ZodTypeAny> = DeepUnknownKeys<T, 'strip'>;
function deepStrip<T extends ZodTypeAny>(schema: T): DeepStrip<T> {
return deepApplyObject(schema, (s) => s.strip()) as DeepStrip<T>;
}
type DeepStrict<T extends ZodTypeAny> = DeepUnknownKeys<T, 'strict'>;
function deepStrict<T extends ZodTypeAny>(
schema: T,
error?: errorUtil.ErrMessage
): DeepStrict<T> {
return deepApplyObject(schema, (s) => s.strict(error)) as DeepStrict<T>;
} Note that I've left some And that's it. Sample usage: const schema = z.object({
a: z.string(),
b: z.object({
a: z.string(),
b: z.array(
z.object({
a: z.string(),
})
),
}),
});
const value = {
a: 'value',
b: {
a: 'value',
b: [{ a: 'value' }, { a: 'value', c: 'unknown' }],
c: 'unknown',
},
c: 'unknown',
};
const stripSchema = deepStrip(schema);
console.log(JSON.stringify(stripSchema.parse(value)));
// => {"a":"value","b":{"a":"value","b":[{"a":"value"},{"a":"value"}]}}
const passthroughSchema = deepPassthrough(schema);
console.log(JSON.stringify(passthroughSchema.parse(value)));
// => {"a":"value","b":{"a":"value","b":[{"a":"value"},{"a":"value","c":"unknown"}],"c":"unknown"},"c":"unknown"}
const strictSchema = deepStrict(schema, 'ERROR');
console.log(JSON.stringify(strictSchema.parse(value)));
// => throws |
Caveat: the implementation provided by @gawi suffers the same shortcomings as
The policy of a recursive type (defined with |
Zod intentionally avoids "modes" and contextual validation - it's too hard to reason about. Using |
For consuming APIs I would almost always pick z.object, but for data exploration as I am doing now z.strictObject is necessary and I am glad I found about it. Of course if you want highly focused lib (on consuing known APIs) with great DX than it may not make sense to add it. Just sharing my pov. |
TypeScript's structural-based type system makes it easy to accidentally leak data (e.g. include password hashes in an HTTP response after fetching a User row from a database table) when building web applications. Thanks to this issue, I know about Defining all the objects to be I'm therefore writing this comment to +1 the idea of adding P.S. I'm a long time zod fan, first time sharing some feedback -- thanks a lot of writing and maintaining this high quality library over the years! |
Hi, I would like to either have strict on all of my types or something like
.deepStrict()
that propagates down on all sub objects in my schema. Now it's too easy to miss out on defining a schema as strict I think.The text was updated successfully, but these errors were encountered: