colinhacks / zod

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

Problem with validation of identical fields in the schema #3752

Open gorinovsoft opened 1 week ago

gorinovsoft commented 1 week ago

Currently, there is an issue in the schema where, if identical fields are present in the user's schemas, Zod incorrectly validates the second testProperty field. Is there a way to fix this? At the moment, the only solution I've found is to change the field name (testProperty) to a unique one.

https://codesandbox.io/p/sandbox/zodschemas-forked-76tlcr?file=%2Fsrc%2FApp.tsx Снимок экрана 2024-09-12 115322

`import { useCallback, VFC } from "react"; import { SubmitHandler, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod";

const stringSchema = z.string().min(1, { message: "fieldIsRequired" });

const firstUser = z.object({ firstName: stringSchema, lastName: stringSchema, testProperty: stringSchema, });

const secondUser = z.object({ firstName: stringSchema, lastName: stringSchema, testProperty: stringSchema.or(z.string().length(0)), testPropertySecond: stringSchema.or(z.string().length(0)), });

const UserSchema = z.union([firstUser, secondUser]);

const usersSchema = z.object({ users: UserSchema.array(), });

type UsersForm = z.infer;

export const App: VFC = () => { const { register, handleSubmit, formState, control } = useForm({ resolver: zodResolver(usersSchema), defaultValues: { users: [ { firstName: "", lastName: "", testProperty: "", }, { firstName: "", lastName: "", testProperty: "", testPropertySecond: "", }, ], }, });

const errors = formState.errors;

console.log(errors, formState, control);

const onSubmit: SubmitHandler = useCallback(async (value) => {}, []);

return ( <form onSubmit={handleSubmit(onSubmit)} style={{ display: "flex", flexDirection: "column", width: 512, margin: "0 auto", }}

  <label style={{ marginTop: "40px" }}>
    <span>First name second user</span>
    <input {...register("users.1.firstName")} />
    <span>{errors?.users?.[1]?.firstName?.message}</span>
  </label>
  <label>
    <span>Last name second user</span>
    <input {...register("users.1.lastName")} />
    <span>{errors?.users?.[1]?.lastName?.message}</span>
  </label>
  <label>
    <span>Test property second user</span>
    <input {...register("users.1.testProperty")} />
    <span>{errors?.users?.[1]?.testProperty?.message}</span>
  </label>
  <label>
    <span>Second test property second user</span>
    <input {...register("users.1.testPropertySecond")} />
    <span>{errors?.users?.[1]?.testPropertySecond?.message}</span>
  </label>
  <button type="submit">Submit</button>
</form>

); }; `

sunnylost commented 1 week ago

You can switch firstUser and secondUser position in union.

const UserSchema = z.union([secondUser, firstUser]);

Zod will iterate through all the options in a union array. If any of the options pass validation, the entire result is considered valid. However, if any of the options fail validation, it will return the result of the first invalid option and ignore the rest.

For example, if you use z.union([firstUser, secondUser]) to parse { firstName: "", lastName: "", testProperty: "" }, the firstUser schema will raise 3 errors, so that becomes the result. Even if you try parsing { firstName: "", lastName: "", testProperty: "", testPropertySecond: "" }, the outcome remains the same. This happens because firstUser is the first option evaluated, and Zod will return its result, ignoring the secondUser schema.

Amirika18 commented 1 week ago

@sunnylost it could be a working solution, but if we change the order of zod schemas in the array and then decide to add a property with the same key but with different validation schema, validation will raise error in the testPropertySecond property. Take a look at this example below: image

sunnylost commented 1 week ago

So the issue is that you want to use the firstUser schema to parse the first user and the secondUser schema for the second, but since both users have the same structure, there's no way to determine which schema to apply. In my opinion, each user should have a property to distinguish them, allowing you to use a discriminated union for validation.

Amirika18 commented 1 week ago

So the issue is that you want to use the firstUser schema to parse the first user and the secondUser schema for the second, but since both users have the same structure, there's no way to determine which schema to apply. In my opinion, each user should have a property to distinguish them, allowing you to use a discriminated union for validation.

Yes, you are right! We want to apply the schema to object depends on some key (for example, id from enum). Unfortunately, zod union do not support the determination of validation schema according to key property and its value. We have z.discriminatedUnion which could help if it could work with transformed zod schemas.