zod icon indicating copy to clipboard operation
zod copied to clipboard

`discriminatedUnion` produces TS error when `.default` or `.preprocess` are applied

Open RobertCraigie opened this issue 2 years ago • 10 comments

Playground link.

Given this snippet:

import { z } from 'zod';

const FooSchema = z.object({
  type: z.literal('foo').default('foo'),
  a: z.string(),
});

const BarSchema = z.object({
  type: z.literal('custom'),
  method: z.string(),
});

const BazSchema = z.discriminatedUnion('type', [FooSchema, BarSchema]);
console.log(BazSchema.parse({ a: 'foo' }));

TypeScript produces this error:

$ tsc tmp.ts
tmp.ts:13:49 - error TS2322: Type 'ZodObject<{ type: ZodDefault<ZodLiteral<"foo">>; a: ZodString; }, "strip", ZodTypeAny, { type?: "foo"; a?: string; }, { type?: "foo"; a?: string; }>' is not assignable to type 'ZodDiscriminatedUnionOption<"type", Primitive>'.
  Type '{ type: z.ZodDefault<z.ZodLiteral<"foo">>; a: z.ZodString; }' is not assignable to type '{ type: ZodLiteral<Primitive>; } & ZodRawShape'.
    Type '{ type: z.ZodDefault<z.ZodLiteral<"foo">>; a: z.ZodString; }' is not assignable to type '{ type: ZodLiteral<Primitive>; }'.
      Types of property 'type' are incompatible.
        Property 'value' is missing in type 'ZodDefault<ZodLiteral<"foo">>' but required in type 'ZodLiteral<Primitive>'.

13 const BazSchema = z.discriminatedUnion('type', [FooSchema, BarSchema]);
                                                   ~~~~~~~~~

  node_modules/zod/lib/types.d.ts:531:9
    531     get value(): T;
                ~~~~~
    'value' is declared here.


Found 1 error in tmp.ts:13

This code works at runtime however and Zod correctly parses the object:

{ type: 'foo', a: 'foo' }

Previously opened as #1263 but was closed as stale.

RobertCraigie avatar Oct 13 '22 14:10 RobertCraigie

Seems like there is a same problem if you refine one of the object schemas inside a union as well:

z.discriminatedUnion('type', [
  z.strictObject({
    type: z.literal('foo'),
  }),
  z.strictObject({
    type: z.literal('bar'),
  }).refine((input) => true, 'Error'),
]);

This one produces an error:

Type 'ZodEffects<ZodObject<{ type: ZodLiteral<"bar">; }, "strict", ZodTypeAny, { type: "bar"; }, { type: "bar"; }>, { type: "bar"; }, { type: "bar"; }>' is missing the following properties from type 'ZodObject<{ type: ZodLiteral<Primitive>; } & ZodRawShape, any, any, { [x: string]: any; }, { [x: string]: any; }>': _cached, _getCached, shape, strict, and 14 more.
'input' is declared but its value is never read.

And here's an ugly workaround for the refinement problem:

const bar = 
  z.strictObject({
    type: z.literal('bar'),
  }).refine((input) => true, 'Error');

type ExtractRefinementType<T extends z.ZodTypeAny> = T extends z.ZodEffects<
  infer T,
  any,
  any
>
  ? ExtractRefinementType<T>
  : T;

z.discriminatedUnion('type', [
  z.strictObject({
    type: z.literal('foo'),
  }),
  bar as unknown as ExtractRefinementType<typeof bar>
]);

luixo avatar Oct 17 '22 11:10 luixo

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 15 '23 11:01 stale[bot]

This would still be very nice to have!

RobertCraigie avatar Jan 15 '23 17:01 RobertCraigie

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 17 '23 17:04 stale[bot]

@colinhacks I believe this may be marked as good first issue.

luixo avatar Apr 17 '23 21:04 luixo

Hey @RobertCraigie 👋 When I try the exact same approach you've mentioned I receive a runtime zod error of:

ZodError: [
      {
        "code": "invalid_union_discriminator",
        "options": [
          "foo",
          "custom"
        ],
        "path": [
          "type"
        ],
        "message": "Invalid discriminator value. Expected 'foo' | 'custom'"
      }
    ]

I was trying to default to a specific zod object if the type (in your example) is undefined, but it doesn't seem to be straightforward. Sadly z.discriminatedUnion doesn't seem to work, but the normal z.union seems to work fine.

doteric avatar May 19 '23 09:05 doteric

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 17 '23 13:08 stale[bot]

Not stale

luixo avatar Aug 17 '23 13:08 luixo

Still seeing this error when trying to apply a .default to discriminatedUnion as of v3.22.4.

grunklejp avatar Mar 12 '24 15:03 grunklejp

Still happening with refine too

flodlc avatar Aug 17 '24 11:08 flodlc