-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
v4: what is the replacement for using z.function as a schema? #4143
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
Comments
Any update on this? Cannot even use |
@AndrewIngram @sanadriu What you can do is wrap the Implementationconst functionSchema = <T extends z.core.$ZodFunction>(schema: T) =>
z.custom<Parameters<T["implement"]>[0]>((fn) => schema.implement(fn))
const createAsyncFunctionSchema = <T extends z.core.$ZodFunction>(schema: T) =>
z.custom<Parameters<T["implementAsync"]>[0]>((fn) => schema.implementAsync(fn)) Exampleconst DynamicEnvVarSchema = z.object({
from: z.literal("dynamic"),
evaluate: createAsyncFunctionSchema(z.function({ input: z.tuple([]), output: z.promise(z.string()) })),
})
type DynamicEnvVar = z.output<typeof DynamicEnvVarSchema> // OutputType
type DynamicEnvVar = {
from: "dynamic";
evaluate: z.core.$InferInnerFunctionTypeAsync<z.ZodTuple<[], null>, z.ZodPromise<z.ZodString>>;
} |
A real pity it requires a workaraound in v4 |
I may be dumb, can I make it allow function parameters? |
If people want to chime in with their uses cases, go for it. The vast majority of uses cases Ive seen in the wild are anti-patterns. Open to being convinced otherwise. |
I've been using this in v3 as a way to allow object properties to be a specific type OR a function that returns that type. Tried out v4 and I can no longer do this (at least the way I'm currently doing it). Simple example from v3: z.object({
name: z.union([
z.string(),
z.function().args(z.any()).returns(z.string()),
])
}) Even in this case, it doesn't actually give an error when parsing, but it will afterwards when attempting to run the function and its arguments/return type don't match. Trying in v4 results in an error: z.object({
name: z.union([
z.string(),
z.function({
input: [z.any()],
output: z.string(),
}),
])
})
If there's a better approach for this functionality, I'd be glad to hear about it. |
I gave up on strictly validating functions, and ended up using z.any() and interface like this... export const ProjectConfigSchema = z.object({
/** Path to base configuration file to inherit from. */
extends: z.string().optional(),
buildConfig: BuildConfigSchema.optional(),
});
export interface ProjectConfig extends z.output<typeof ProjectConfigSchema> {
buildConfig?: BuildConfig;
} |
Starting with
|
I have a bunch of scripts, all of which run regularly on some input data. Each script has a big .js config, which is validated by Zod. In one place, I save files, and file names are dependant upon input data of the script. I need config parameter like fileName: (input) => `${input.param1}_Name_${input.param2}` And I want to change this pattern willy-nilly between scripts. In the second place, I send the file over email.
But when I'm in dev and I don't want to bother with providing correct email in the input data, I just change it to
I don't need Zod to verify inputs and outputs of the function parameters, but I at least want to have it verify type |
I've been using Zod 3 function schemas as a way to help type check dynamic imports; import { z } from 'zod';
const adapter = z.object({
methodA: z.function().args(z.string).returns(z.unknown().promise())
// ... many other methods ...
});
export type Adapter = z.output<typeof adapter>;
const adapterModule = z.object({
init: z.function().returns(adapter)
});
export async function loadAdapter (): Promise<Adapter> {
const adapter = adapterModule.parse(
await import(`./adapters/${ADAPTER}.mjs`)
);
return adapter.init();
} Each The With this design I can support different data storage layers, as long as they conform to the switch over Adapter :
export async function loadAdapter() {
switch (ADAPTER) {
case 'datasourceA': {
const adapter = await import('./adapters/datasourceA.mjs');
return adapter.init();
}
// ... repeat per each adapter ...
}
} This should work but it's less clean... |
I, too, ran into this missing legacy behavior, found this thread, and then also had the problem with the tuple arguments when a non-empty. I was (eventually) able to work around that piece - awkwardly; I believe it has to do with Typescript not figuring out the nested generics off of defaults. (lower down). But now I've run into an extra layer of problem: the zod inference layer doesn't penetrate deep enough into the The culprit for the args - at least for me and the version of Zod/Typescript I was using was that I needed to give it more assistance with the Schema types. I created a wrapper that lets me have the equivalent functionality from before:
I'll leave the async version as an exercise to the reader. Alternatively, you could try switching to use Array instead of Tuple which similarly simplifies the types - but requires arguments to be simpler. |
Part2:
What am I doing? My code is admittedly a little overcomplicated, but we're combining two situations.
The combination of these two things means that I need Zod inference on my method types (much like Vladimir) but also that these methods can be recognized and called safely. In v3 I was able to accomplish this without issue - including safe type inference, but with v4 I'm no longer able to do that. Here are a couple of playgrounds illustrating the problem - the majority of the code is the same, except for the zod version + isFunction setup -> and then resulting error. While I realize this example is somewhat contrived, it's actually not that far off (aside from reducing the complexity) from the code that I'm trying to upgrade. |
I'm using zod to validate the global environment my code is executing within like this: export type ReactSwiftUIElement = {
id: string
type: string
addChild(child: ReactSwiftUIElement): void
removeChild(child: ReactSwiftUIElement): void
updateProps(properties: Record<string, any>): void
updateValue(value: string): void
commitUpdate(): void
createElement(type: string): ReactSwiftUIElement | null
}
export const ReactSwiftUIElementSchema: z.ZodType<ReactSwiftUIElement> = z.lazy(() =>
z.object({
id: z.string(),
type: z.string(),
addChild: z.function().args(ReactSwiftUIElementSchema).returns(z.void()),
removeChild: z.function().args(ReactSwiftUIElementSchema).returns(z.void()),
updateProps: z.function().args(z.record(z.string(), z.any())).returns(z.void()),
updateValue: z.function().args(z.string()).returns(z.void()),
commitUpdate: z.function().args().returns(z.void()),
createElement: z
.function()
.args(z.string())
.returns(z.lazy(() => ReactSwiftUIElementSchema).nullable()),
})
)
export const ViewableHostSchema = z.object({
element: ReactSwiftUIElementSchema,
}) Perhaps it's excessively conservative but I certainly wouldn't call this an "anti-pattern". Need v3-like function schemas in v4 to upgrade here. |
Uh oh!
There was an error while loading. Please reload this page.
The docs say this
We have Zod 3 code like this, which is used to give us type-safety (though presumably not runtime safety) that a function matches a shape):
What would be the most appropriate way to do this in Zod 4?
The text was updated successfully, but these errors were encountered: