Table of Contents:
const UserSchema = z.object({
name: z.string(),
email: z.string().email(),
address: z.object({
street: z.string(),
city: z.string(),
}),
});
type UserSchemaType = z.infer<typeof UserSchema>;
// Example usage
const result = UserSchema.safeParse({
name: "",
email: "invalid-email",
address: {
street: 453, // should be a string
city: 123, // should be a string
},
});
if (!result.success) {
console.log("Zod error: ", JSON.stringify(result.error, null, 2));
console.log("Zod error.format(): ", JSON.stringify(result.error.format(), null, 2));
console.log("Zod error.errors: ", JSON.stringify(result.error.errors, null, 2));
console.log("Zod error.flatten(): ", JSON.stringify(result.error.flatten(), null, 2));
}
{
"issues": [
{
"validation": "email",
"code": "invalid_string",
"message": "Invalid email",
"path": [
"email"
]
},
{
"code": "invalid_type",
"expected": "string",
"received": "number",
"path": [
"address",
"street"
],
"message": "Expected string, received number"
},
{
"code": "invalid_type",
"expected": "string",
"received": "number",
"path": [
"address",
"city"
],
"message": "Expected string, received number"
}
],
"name": "ZodError"
}
{
"_errors": [],
"email": {
"_errors": [
"Invalid email"
]
},
"address": {
"_errors": [],
"street": {
"_errors": [
"Expected string, received number"
]
},
"city": {
"_errors": [
"Expected string, received number"
]
}
}
}
// Returns an array
[
{
validation: "email",
code: "invalid_string",
message: "Invalid email",
path: ["email"],
},
{
code: "invalid_type",
expected: "string",
received: "number",
path: ["address", "street"],
message: "Expected string, received number",
},
{
code: "invalid_type",
expected: "string",
received: "number",
path: ["address", "city"],
message: "Expected string, received number",
},
];
// Doesn't work well with nested fields
{
"formErrors": [],
"fieldErrors": {
"email": [
"Invalid email"
],
"address": [
"Expected string, received number",
"Expected string, received number"
]
}
}
Function
// Inspired from Next-Safe-Action
import { z } from "zod";
type ObjectLiteral = Record<string, any>;
type ValidationErrors<TSchema extends ObjectLiteral | undefined> = {
[K in keyof TSchema]?: TSchema[K] extends ObjectLiteral ? ValidationErrors<TSchema[K]> : string[];
} & {
_errors?: string[];
};
export function formatZodError<TSchema extends ObjectLiteral | undefined>(
zodError: z.ZodError
): ValidationErrors<TSchema> {
const validationErrors: ValidationErrors<TSchema> = {};
for (const issue of zodError.issues) {
let current: any = validationErrors;
for (let i = 0; i < issue.path.length; i++) {
const key = issue.path[i];
if (typeof key !== "string" && typeof key !== "number") continue;
if (i === issue.path.length - 1) {
// We're at the leaf of the path
if (!current[key]) {
current[key] = [];
}
if (Array.isArray(current[key])) {
current[key].push(issue.message);
}
} else {
// We're still traversing the path
if (!current[key] || typeof current[key] !== "object") {
current[key] = {};
}
current = current[key];
}
}
// Handle root-level errors
if (issue.path.length === 0) {
if (!validationErrors._errors) {
validationErrors._errors = [];
}
validationErrors._errors.push(issue.message);
}
}
return validationErrors;
}
if (!result.success) {
const validationErrors = zodErrorToValidationErrors<UserSchemaType>(result.error);
console.log("Custom error: ", validationErrors);
}
Output:
{
"email": ["Invalid email"],
"address": {
"street": ["Expected string, received number"],
"city": ["Expected string, received number"]
}
}