zod icon indicating copy to clipboard operation
zod copied to clipboard

Defining function rest parameters

Open weary-pilgrim opened this issue 2 years ago • 2 comments

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

weary-pilgrim avatar Oct 11 '23 17:10 weary-pilgrim

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())

rotu avatar Nov 13 '23 05:11 rotu

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...

Downchuck avatar Aug 30 '24 00:08 Downchuck

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.

rotu avatar Sep 01 '24 04:09 rotu

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.

Downchuck avatar Sep 02 '24 02:09 Downchuck

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.

Downchuck avatar Jan 11 '25 21:01 Downchuck

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  Join Discord Share on X

dosubot[bot] avatar Jul 22 '25 03:07 dosubot[bot]

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

colinhacks avatar Jul 25 '25 08:07 colinhacks