colinhacks / zod

TypeScript-first schema validation with static type inference
https://zod.dev
MIT License
33.98k stars 1.19k forks source link

[Bug ?] Zod union ignoring zod object when fields are z.optional() #3562

Open aaalloc opened 5 months ago

aaalloc commented 5 months ago

Hi,

Basically, when doing zod objects and doing an union on that, when the second object of the union has optional fields, parsing just ignore it ! Is that normal ? I can't find any information about that. I've came with a solution using discriminatedUnion but that feels a little bit dirty ...

I've created a reproduction environment for replaying the issue
https://stackblitz.com/edit/typescript-f8ppcz?file=schemas%2FschemaWithDefaultType.ts

import { z } from 'zod';

const say = console.info;

function zod_optional_union_ignored() {
  // ! when one and two are optional,
  const type2 = z.object({
    one: z.number().optional(),
    two: z.number().optional(),
  });

  const type1 = z.object({
    three: z.number().optional(),
  });

  const type2or1 = z.union([type1, type2]);

  const test_data = {
    one: 1,
    two: 2,
  };

  const validatedTestData = type2or1.parse(test_data);

  // ! type2 is ignored, why is that ??
  say('zod optional type2 object ignored', { validatedTestData });

  const test_data2 = {
    three: 3,
  };

  // type1 is parsed correctly
  const validatedTestData2 = type2or1.parse(test_data2);
  say('zod optional type1 object not ignored', { validatedTestData2 });
}

function zod_union_not_ignored() {
  const type2 = z.object({
    one: z.number(),
    two: z.number(),
  });

  const type1 = z.object({
    three: z.number(),
  });

  const type2or1 = z.union([type1, type2]);

  const test_data = {
    one: 3,
    two: 2,
  };

  const validatedTestData = type2or1.parse(test_data);

  // type2 is parsed correctly here, certainly because there is no optional
  say('zod optional ingored', { validatedTestData });
}

zod_optional_union_ignored();
zod_union_not_ignored();

image

samchungy commented 5 months ago

It's passing because your payload is valid against the first object in the union. You could fix this by enforcing strict objects

aaalloc commented 5 months ago

It's passing because your payload is valid against the first object in the union. You could fix this by enforcing strict objects

But why in the first section type2 is ignored ?

samchungy commented 5 months ago

Because it successfully validated against the first object.

Zod will test the input against each of the "options" in order and return the first value that validates successfully.