Proper introspection APIs for handy utils? #1763
Replies: 7 comments 6 replies
-
Also discussed in #1681. |
Beta Was this translation helpful? Give feedback.
-
Here's how I would do it. I think it's a bit cleaner and no messing around with pseudo-private properties 😀. Also it allows you to use Let me know if you have any questions. function valuesFromSearchParams ( searchParams: URLSearchParams ) {
return Array.from( searchParams.keys() ).reduce( ( values, key ) => ( {
...values, [ key ]: searchParams.getAll( key )
} ), {} as Record<string, Array<string> | string> )
}
const searchParamSchema = z.string().array().min( 1 )
const schema = z.object( {
many: searchParamSchema,
string: searchParamSchema.max( 1 ).pipe(
z.coerce.string()
),
posNumber: searchParamSchema.max( 1 ).pipe(
z.coerce.number().positive()
),
range: searchParamSchema.max( 1 ).pipe(
z.coerce.number().min( 0 ).max( 5 )
),
plainDate: searchParamSchema.max( 1 ).pipe(
z.coerce.string().refine(
v => /\d{4}-\d{2}-\d{2}/.test( v ),
val => ( { message: `Invalid plain date: ${ val }` } ),
),
),
} )
const searchParams = new URLSearchParams( [
[ 'many', 'hello' ],
[ 'many', 'world' ],
[ 'string', 'foo' ],
[ 'posNumber', '42' ],
] )
searchParams.set( 'range', '4' )
searchParams.set( 'plainDate', '2021-01-01' )
const values = valuesFromSearchParams( searchParams )
console.log( schema.parse( values ) )
// {
// many: [ 'hello', 'world' ],
// string: 'foo',
// posNumber: 42,
// range: 4,
// plainDate: '2021-01-01'
// } |
Beta Was this translation helpful? Give feedback.
-
Thanks! I'll give you suggestion a look! |
Beta Was this translation helpful? Give feedback.
-
@kentcdodds |
Beta Was this translation helpful? Give feedback.
-
I was about to reinvent the wheel doing the same thing, thanks for your contribution! It works as a champ! 🎉 |
Beta Was this translation helpful? Give feedback.
-
This thread is stale but in case anyone else stumbles on this like I did, I also wanted to inspect the schema but for a more specific use case. This is what I came up with, it doesn't cover everything because my use case doesn't require it. (thankfully, because there're a lot more classes to check) I also had to use const ExampleSchema = z.object({
a: z.string(),
b: z.string().optional(),
c: z.string().refine((val) => typeof val === "string"),
d: z.string().transform((val) => val),
e: z.string().array(),
f: z
.string()
.transform((val) => val.length)
.pipe(z.number().min(5)),
g: z
.string()
.min(1)
.transform((val) => val.length)
.pipe(z.number().min(5))
.array()
.refine((val) => val)
.nullish(),
});
console.log("Naively comparing");
console.log(ExampleSchema.shape.a instanceof ZodString); // true
console.log(ExampleSchema.shape.b instanceof ZodString); // false
console.log(ExampleSchema.shape.c instanceof ZodString); // false
console.log(ExampleSchema.shape.d instanceof ZodString); // false
console.log(ExampleSchema.shape.e instanceof ZodString); // false
console.log(ExampleSchema.shape.f instanceof ZodString); // false
console.log(ExampleSchema.shape.g instanceof ZodString); // false
console.log("Do some recursive type checks");
const findBaseType = (schema: ZodSchema<any>) => {
if (schema instanceof ZodEffects) {
return findBaseType(schema.sourceType());
}
if (schema instanceof ZodOptional || schema instanceof ZodNullable) {
return findBaseType(schema.unwrap());
}
if (schema instanceof ZodPipeline) {
return findBaseType(schema._def.in);
}
if (schema instanceof ZodArray) {
return findBaseType(schema.element);
}
return schema;
};
console.log(findBaseType(ExampleSchema.shape.a) instanceof ZodString); // true
console.log(findBaseType(ExampleSchema.shape.b) instanceof ZodString); // true
console.log(findBaseType(ExampleSchema.shape.c) instanceof ZodString); // true
console.log(findBaseType(ExampleSchema.shape.d) instanceof ZodString); // true
console.log(findBaseType(ExampleSchema.shape.e) instanceof ZodString); // true
console.log(findBaseType(ExampleSchema.shape.f) instanceof ZodString); // true
console.log(findBaseType(ExampleSchema.shape.g) instanceof ZodString); // true Codesandbox if it helps anyone |
Beta Was this translation helpful? Give feedback.
-
@colinhacks I just want to confirm that I'm currently building a tool that uses Zod objects as their schema "source of truth" and translates them into related objects/types for use with other modules/services. Just want to make sure that won't break any time soon. |
Beta Was this translation helpful? Give feedback.
-
I'd like to be able to parse searchParams with Zod. Here's a utility I built for that:
The tricky bit is the fact that searchParams can have multiple of the same key (so you can't just do
searchParams.entries()
). So what my utility does is to first get all the properties on searchParams withgetAll
, then determines whether you were hoping for an array of values and if you are it will double check there's only one value and then add only one value to the resulting object. If you're hoping for an array then it leaves it as an array. If you were not hoping for an array but there's more than one value it leaves it as an array and the laterparse
call will result in an error (as desired).My problem is that to determine whether you want an array I need to inspect the schema you've provided and it looks like I'm using a pseudo-private API for that (and TypeScript isn't happy with that). Specifically the
_def
property.Is there a better way to do what I'm doing?
EDIT: I changed from
schema: z.ZodSchema<SchemaType>
toschema: z.ZodObject<SchemaType>
and that made TypeScript much happier. But I still want to make sure that there's no better way to do this since I'm using a pseudo-private property 😅EDIT 2: I handled other zod types that wrap the array type so you can use
default
,optional
,union
etc.Beta Was this translation helpful? Give feedback.
All reactions