diff --git a/deno/lib/ZodError.ts b/deno/lib/ZodError.ts index b16711c7b..beb328653 100644 --- a/deno/lib/ZodError.ts +++ b/deno/lib/ZodError.ts @@ -165,12 +165,12 @@ export const quotelessJson = (obj: any) => { export type ZodFormattedError = { _errors: U[]; -} & (T extends [any, ...any[]] - ? { [K in keyof T]?: ZodFormattedError } - : T extends any[] - ? { [k: number]: ZodFormattedError } - : T extends object - ? { [K in keyof T]?: ZodFormattedError } +} & (NonNullable extends [any, ...any[]] + ? { [K in keyof NonNullable]?: ZodFormattedError[K]> } + : NonNullable extends any[] + ? { [k: number]: ZodFormattedError[number]> } + : NonNullable extends object + ? { [K in keyof NonNullable]?: ZodFormattedError[K]> } : unknown); export type inferFormattedError< diff --git a/deno/lib/__tests__/error.test.ts b/deno/lib/__tests__/error.test.ts index 85c37a12b..9521b598b 100644 --- a/deno/lib/__tests__/error.test.ts +++ b/deno/lib/__tests__/error.test.ts @@ -316,6 +316,47 @@ test("formatting", () => { } }); +test("formatting with nullable and optional fields", () => { + const nameSchema = z.string().refine((val) => val.length > 5); + const schema = z.object({ + nullableObject: z.object({ name: nameSchema }).nullable(), + nullableArray: z.array(nameSchema).nullable(), + nullableTuple: z.tuple([nameSchema, nameSchema, z.number()]).nullable(), + optionalObject: z.object({ name: nameSchema }).optional(), + optionalArray: z.array(nameSchema).optional(), + optionalTuple: z.tuple([nameSchema, nameSchema, z.number()]).optional(), + }); + const invalidItem = { + nullableObject: { name: "abcd" }, + nullableArray: ["abcd"], + nullableTuple: ["abcd", "abcd", 1], + optionalObject: { name: "abcd" }, + optionalArray: ["abcd"], + optionalTuple: ["abcd", "abcd", 1], + }; + const result = schema.safeParse(invalidItem); + expect(result.success).toEqual(false); + if (!result.success) { + type FormattedError = z.inferFormattedError; + const error: FormattedError = result.error.format(); + expect(error._errors).toEqual([]); + expect(error.nullableObject?._errors).toEqual([]); + expect(error.nullableObject?.name?._errors).toEqual(["Invalid input"]); + expect(error.nullableArray?._errors).toEqual([]); + expect(error.nullableArray?.[0]?._errors).toEqual(["Invalid input"]); + expect(error.nullableTuple?._errors).toEqual([]); + expect(error.nullableTuple?.[0]?._errors).toEqual(["Invalid input"]); + expect(error.nullableTuple?.[1]?._errors).toEqual(["Invalid input"]); + expect(error.optionalObject?._errors).toEqual([]); + expect(error.optionalObject?.name?._errors).toEqual(["Invalid input"]); + expect(error.optionalArray?._errors).toEqual([]); + expect(error.optionalArray?.[0]?._errors).toEqual(["Invalid input"]); + expect(error.optionalTuple?._errors).toEqual([]); + expect(error.optionalTuple?.[0]?._errors).toEqual(["Invalid input"]); + expect(error.optionalTuple?.[1]?._errors).toEqual(["Invalid input"]); + } +}) + const stringWithCustomError = z.string({ errorMap: (issue, ctx) => ({ message: diff --git a/src/ZodError.ts b/src/ZodError.ts index 85e58b6e8..992f23d85 100644 --- a/src/ZodError.ts +++ b/src/ZodError.ts @@ -165,12 +165,12 @@ export const quotelessJson = (obj: any) => { export type ZodFormattedError = { _errors: U[]; -} & (T extends [any, ...any[]] - ? { [K in keyof T]?: ZodFormattedError } - : T extends any[] - ? { [k: number]: ZodFormattedError } - : T extends object - ? { [K in keyof T]?: ZodFormattedError } +} & (NonNullable extends [any, ...any[]] + ? { [K in keyof NonNullable]?: ZodFormattedError[K]> } + : NonNullable extends any[] + ? { [k: number]: ZodFormattedError[number]> } + : NonNullable extends object + ? { [K in keyof NonNullable]?: ZodFormattedError[K]> } : unknown); export type inferFormattedError< diff --git a/src/__tests__/error.test.ts b/src/__tests__/error.test.ts index 54eec4386..cc94050db 100644 --- a/src/__tests__/error.test.ts +++ b/src/__tests__/error.test.ts @@ -315,6 +315,47 @@ test("formatting", () => { } }); +test("formatting with nullable and optional fields", () => { + const nameSchema = z.string().refine((val) => val.length > 5); + const schema = z.object({ + nullableObject: z.object({ name: nameSchema }).nullable(), + nullableArray: z.array(nameSchema).nullable(), + nullableTuple: z.tuple([nameSchema, nameSchema, z.number()]).nullable(), + optionalObject: z.object({ name: nameSchema }).optional(), + optionalArray: z.array(nameSchema).optional(), + optionalTuple: z.tuple([nameSchema, nameSchema, z.number()]).optional(), + }); + const invalidItem = { + nullableObject: { name: "abcd" }, + nullableArray: ["abcd"], + nullableTuple: ["abcd", "abcd", 1], + optionalObject: { name: "abcd" }, + optionalArray: ["abcd"], + optionalTuple: ["abcd", "abcd", 1], + }; + const result = schema.safeParse(invalidItem); + expect(result.success).toEqual(false); + if (!result.success) { + type FormattedError = z.inferFormattedError; + const error: FormattedError = result.error.format(); + expect(error._errors).toEqual([]); + expect(error.nullableObject?._errors).toEqual([]); + expect(error.nullableObject?.name?._errors).toEqual(["Invalid input"]); + expect(error.nullableArray?._errors).toEqual([]); + expect(error.nullableArray?.[0]?._errors).toEqual(["Invalid input"]); + expect(error.nullableTuple?._errors).toEqual([]); + expect(error.nullableTuple?.[0]?._errors).toEqual(["Invalid input"]); + expect(error.nullableTuple?.[1]?._errors).toEqual(["Invalid input"]); + expect(error.optionalObject?._errors).toEqual([]); + expect(error.optionalObject?.name?._errors).toEqual(["Invalid input"]); + expect(error.optionalArray?._errors).toEqual([]); + expect(error.optionalArray?.[0]?._errors).toEqual(["Invalid input"]); + expect(error.optionalTuple?._errors).toEqual([]); + expect(error.optionalTuple?.[0]?._errors).toEqual(["Invalid input"]); + expect(error.optionalTuple?.[1]?._errors).toEqual(["Invalid input"]); + } +}) + const stringWithCustomError = z.string({ errorMap: (issue, ctx) => ({ message: