Defining function rest parameters
This is essentially the same issue as the closed #1282. I have a function that is shaped like this:
export type MyFunc = (...args: string[]) => object;
I tried a couple of the solutions offered in the original issues.
Attempt 1
This is my attempt to use the original solution offered by @colinhacks:
import { z } from 'zod';
const MyFuncSchema = z
.function()
.args(z.tuple([]).rest(z.string()))
.returns(z.void());
export type MyFunc = z.infer<typeof MyFuncSchema>;
const myFunc: MyFunc = (...args) => {
args.forEach((arg) => {
console.log(arg);
});
};
myFunc('a', 'b', 'c');
When I run I run tsc, I get this error:
my-func.ts:16:8 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'string[]'.
16 myFunc('a', 'b', 'c');
When I hover on the the MyFunc type (line 8) in the above, VSCode kindly tells me that the type resolves to this:
type MyFunc = (args_0: string[], ...args_1: unknown[]) => void
I think this means that Zod interprets arg_0 as the tuple defined in the arguments, and the rest is whatever it is (which makes sense in from the syntax).
Attempt 2
This is my attempt to use the solution offered by @maxArturo:
import { z } from 'zod';
const possibleInputs: [] | [z.ZodString, ...z.ZodString[]] = [
z.string(), //
z.string(),
];
const MyFuncSchema = z
.function() //
.args(...possibleInputs)
.returns(z.void());
export type MyFunc = z.infer<typeof MyFuncSchema>;
const myFunc: MyFunc = (...args) => {
args.forEach((arg) => {
console.log(arg);
});
};
myFunc('a', 'b', 'c');
For now, I'm going with this solution because, as @maxArturo says, this works! However, when I hover on MyFunc, I get this:
type MyFunc = (args_0: string, ...args_1: unknown[]) => void
This means I don't get the strong type checking on values in args_1 that I want. 😢
What I want
What I'd love to have is the ability to define the function schema like this:
const MyFuncSchema = z
.function()
.args().rest(z.string())
.returns(z.void());
This would result in a function type like this:
type MyFunc = (...args_0: string[]) => void
Details
I'm using:
- tsc v5.2.2
- zod v3.22.4
I think you can do this, but not with the fluent .args() method. The underlying reason seems to be that .args() always appends rest unknown (@colinhacks, why?)
import { z } from "zod";
const MyFuncSchema = z.function(
z.tuple([]).rest(z.string()),
z.void()
);
// equivalently, z.function(z.tuple([]).rest(z.string())).returns(z.void())
As with exactPropertyTypes and the conversation -- a function in JavaScript will accept more arguments regardless of the shape.
So it's in that manner that a loose function will always have a rest array. Now most challenging for me is named tuples; tough area as it'd be nice to see the names of the arguments on the function instead of just arg_0, arg_1...
a function in JavaScript will accept more arguments regardless of the shape.
Irrelevant. Typescript allows you to constrain the arguments and it rejects additional arguments by default.
it'd be nice to see the names of the arguments on the function instead of just arg_0, arg_1...
Agreed. But I'm not sure how one would code this up.
a function in JavaScript will accept more arguments regardless of the shape.
Irrelevant. Typescript allows you to constrain the arguments and it rejects additional arguments by default.
At the very least, a .strict ought to be available, but does not seem easily accessible.
it'd be nice to see the names of the arguments on the function instead of just arg_0, arg_1...
Agreed. But I'm not sure how one would code this up.
I don't know that typescript presently supports it -- function is using z.tuple, but I don't believe that z.tuple can expose names as there are no utilities in present typescript to do so. That stated, we could at least get those keys available in the object and accessible through a zod function -- and should typescript add the ability to create labeled tuples through a programmatic interface, it could work out.
Labels are just plain not available at the moment without defining them first in ts.
Some movement in the area of tuples from TS 5.2:
https://github.com/microsoft/TypeScript/issues/39941 https://github.com/microsoft/TypeScript/issues/44939
I don't know if it's there yet (if we need more functionality in dynamic tuples) -- but I do think zod puts forward a strong case for engagement in the area.
You're right—TypeScript lets you constrain function arguments, but Zod's function schemas use tuple types for arguments, which don't preserve parameter names in the inferred types. This is a TypeScript limitation as well: tuple elements are always positional and anonymous, so you get arg_0, arg_1, etc., instead of named parameters.
If you want named arguments with strong typing in Zod, the best approach is to use an object schema as a single parameter. For example:
import { z } from "zod";
const MyFuncSchema = z.function()
.args(z.object({ foo: z.string(), bar: z.number() }))
.returns(z.void());
type MyFunc = z.infer<typeof MyFuncSchema>;
const myFunc: MyFunc = ({ foo, bar }) => {
// foo and bar are named and strongly typed
};
This way, TypeScript and Zod both preserve the argument names and types. There isn't a built-in or documented way to get named arguments with tuple-based function schemas in Zod—this matches how TypeScript itself handles tuples and function signatures. For more, see the Zod function docs.
If this answers your question, feel free to close the issue!
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
Fixed in Zod 4
const variadicTuple = z.tuple([z.string()], z.number());
// => [string, ...number[]];
[0] https://zod.dev/api?id=tuples [1] https://zod.dev/api?id=functions