Ensure uniqueness for zod schema field

Hello! I am using react-hook-form and have the following zod object to validate the input participants:
z.object({
email: z // Should be unique (not same as any other participant.email)
.string()
.email({
message: tParticipant("fields.email.rules.pattern"),
})
.min(1, {
message: tParticipant("fields.email.rules.required"),
}),
name: z // Should be unique (not same as any other participant.name)
.string()
.min(1, { message: tParticipant("fields.name.rules.required") })
})
.array()
z.object({
email: z // Should be unique (not same as any other participant.email)
.string()
.email({
message: tParticipant("fields.email.rules.pattern"),
})
.min(1, {
message: tParticipant("fields.email.rules.required"),
}),
name: z // Should be unique (not same as any other participant.name)
.string()
.min(1, { message: tParticipant("fields.name.rules.required") })
})
.array()
How can I validate using Zod that each email is unique, aswell as each name ? Something like a .unique() is what I am looking for 🙂 Thank you!
8 Replies
Samathingamajig
I think you need to write a custom schema https://zod.dev/?id=custom-schemas
GitHub
TypeScript-first schema validation with static type inference
TypeScript-first schema validation with static type inference
O2K
O2KOP2y ago
Yeah you might be right! Not sure though, since the custom schema would have to evaluate itself, kind of a catch 22 situation 🤔 I might just do the error handling separately on form submission instead 🤷‍♂️
Samathingamajig
ill try making it with zod
Samathingamajig
actually refine might be what we're looking for https://zod.dev/?id=refine
GitHub
TypeScript-first schema validation with static type inference
TypeScript-first schema validation with static type inference
O2K
O2KOP2y ago
Yes! It should be useable on the participants array
Samathingamajig
const schema = z
.object({
email: z // Should be unique (not same as any other participant.email)
.string()
.email({
message: "invalid email",
})
.min(1, {
message: "email is a required field",
}),
name: z // Should be unique (not same as any other participant.name)
.string()
.min(1, { message: "name is a required field" }),
})
.array()
.refine(
(entries) => {
const emails = new Set(entries.map((e) => e.email));
const names = new Set(entries.map((e) => e.name));
return emails.size === entries.length && names.size === entries.length;
},
{
message: "emails and names must be unique",
},
);
const schema = z
.object({
email: z // Should be unique (not same as any other participant.email)
.string()
.email({
message: "invalid email",
})
.min(1, {
message: "email is a required field",
}),
name: z // Should be unique (not same as any other participant.name)
.string()
.min(1, { message: "name is a required field" }),
})
.array()
.refine(
(entries) => {
const emails = new Set(entries.map((e) => e.email));
const names = new Set(entries.map((e) => e.name));
return emails.size === entries.length && names.size === entries.length;
},
{
message: "emails and names must be unique",
},
);
this works if you want more specific details like "names must be unique" and "emails must be unique" you can trivially chain another .refine and move one of them to the other i tested with
const shouldSucceed = schema.safeParse([
{
name: "sam",
},
{
name: "o2k",
},
] satisfies z.infer<typeof schema>);
console.log(shouldSucceed.success); // true

const shouldFail = schema.safeParse([
{
name: "sam",
},
{
name: "sam",
},
{
name: "o2k",
},
] satisfies z.infer<typeof schema>);

console.log(shouldFail.success); // false
const shouldSucceed = schema.safeParse([
{
name: "sam",
},
{
name: "o2k",
},
] satisfies z.infer<typeof schema>);
console.log(shouldSucceed.success); // true

const shouldFail = schema.safeParse([
{
name: "sam",
},
{
name: "sam",
},
{
name: "o2k",
},
] satisfies z.infer<typeof schema>);

console.log(shouldFail.success); // false
ZodError: [
{
"code": "custom",
"message": "emails and names must be unique",
"path": []
}
]
ZodError: [
{
"code": "custom",
"message": "emails and names must be unique",
"path": []
}
]
O2K
O2KOP2y ago
Thank you, this works great!
Samathingamajig
🫡 fix the error messages back to what you had
Want results from more Discord servers?
Add your server