zod icon indicating copy to clipboard operation
zod copied to clipboard

Type error with discriminatedUnion when using dynamic amount of objects

Open ghostdevv opened this issue 3 years ago • 5 comments

For a project I am working on we are using zod to type check user provided input at runtime, we have "sections" which are defined in a manifest. I need to have a discriminatedUnion by am getting a type error when doing the following:

const section = z.discriminatedUnion('type', [
  ...Object.values(manifest.sections).map((section) => section.zod),
])

The above code does work, and what is weirder is that when accessing the items individually it works:

const zodObjects = Object.values(manifest.sections).map((section) => section.zod)

const section = z.discriminatedUnion('type', [
  zodObjects[0],
  zodObjects[1],
  zodObjects[2],
  // ...
])

At a guess it's because it doesn't know that it's a array with multiple items but am unsure the best way of fixing this

ghostdevv avatar May 31 '22 19:05 ghostdevv

This issue is probably the same as https://github.com/colinhacks/zod/issues/989, except this issue is actually open (I can't get anyone to re-open the other issue).

Aaronius avatar Jun 30 '22 14:06 Aaronius

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Aug 30 '22 23:08 stale[bot]

Still relevant

ghostdevv avatar Aug 31 '22 23:08 ghostdevv

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Oct 31 '22 08:10 stale[bot]

🔨

benjick avatar Oct 31 '22 08:10 benjick

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jan 29 '23 09:01 stale[bot]

🔔

Aaronius avatar Jan 29 '23 20:01 Aaronius

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Apr 30 '23 04:04 stale[bot]

Boop

benjick avatar May 30 '23 06:05 benjick

👋

Yeah, the issue here is that we enforce at the type level that the array has at least two items ([T, T, ...T[]]), but the type of Object.values is a (potentially empty) array. You can get around this in a few ways:

  1. Make a function that wraps your Object.values calls and uses a runtime check and assertion that the value has two or more values.
  2. Use some type casting to assert that the Object.values returns [T, T, ...T[]] to tell the compiler that you are absolutely certain it contains at least two values of type T.
  3. Throw caution to the wind and use as any #yolo

import { z } from "zod";

const manifest = {
    sections: [
        z.object({ type: z.literal("main") }),
        z.object({ type: z.literal("nav") }),
        z.object({ type: z.literal("footer") })
    ]
};

function atLeastTwo<T>(arr: T[]): [T, T, ...T[]] {
  if (arr.length < 2) {
    throw new Error("Instantiated with fewer than two items");
  }

  return arr as [T, T, ...T[]];
}

const section = z.discriminatedUnion("type", [
    ...(atLeastTwo(manifest.sections)),
]);

TL;DR: we want to enforce that the array you pass to discriminatedUnion (and a few other constructors!) has at least two values, and the only way to satisfy TypeScript is to actually ensure that statically.

scotttrinh avatar May 30 '23 20:05 scotttrinh