From 3b8abb4ea3bc34c052c6ff535b7a4e696c56ca5c Mon Sep 17 00:00:00 2001 From: Edmund Hung Date: Tue, 10 Sep 2024 23:06:11 +0100 Subject: [PATCH] fix(conform-zod): enable zod object coercion (#733) --- .changeset/tender-kiwis-flash.md | 7 +++++ packages/conform-zod/coercion.ts | 33 ++++++++++++++------- tests/conform-zod.spec.ts | 50 ++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 .changeset/tender-kiwis-flash.md diff --git a/.changeset/tender-kiwis-flash.md b/.changeset/tender-kiwis-flash.md new file mode 100644 index 00000000..b9399ed3 --- /dev/null +++ b/.changeset/tender-kiwis-flash.md @@ -0,0 +1,7 @@ +--- +'@conform-to/zod': patch +--- + +fix(conform-zod): enable zod object coercion + +Conform should support nested fields with only checkboxes now. Fix #391. diff --git a/packages/conform-zod/coercion.ts b/packages/conform-zod/coercion.ts index e9a22a95..91487897 100644 --- a/packages/conform-zod/coercion.ts +++ b/packages/conform-zod/coercion.ts @@ -185,17 +185,28 @@ export function enableTypeCoercion( }), ); } else if (def.typeName === 'ZodObject') { - const shape = Object.fromEntries( - Object.entries(def.shape()).map(([key, def]) => [ - key, - // @ts-expect-error see message above - enableTypeCoercion(def, cache), - ]), - ); - schema = new ZodObject({ - ...def, - shape: () => shape, - }); + schema = any() + .transform((value) => { + if (typeof value === 'undefined') { + // Defaults it to an empty object + return {}; + } + + return value; + }) + .pipe( + new ZodObject({ + ...def, + shape: () => + Object.fromEntries( + Object.entries(def.shape()).map(([key, def]) => [ + key, + // @ts-expect-error see message above + enableTypeCoercion(def, cache), + ]), + ), + }), + ); } else if (def.typeName === 'ZodEffects') { if (isFileSchema(type as unknown as ZodEffects)) { schema = any() diff --git a/tests/conform-zod.spec.ts b/tests/conform-zod.spec.ts index c6e00334..882a1b4e 100644 --- a/tests/conform-zod.spec.ts +++ b/tests/conform-zod.spec.ts @@ -524,6 +524,56 @@ describe('conform-zod', () => { }); }); + test('z.object', () => { + const schema = z.object({ + a: z.object({ + text: z.string({ + required_error: 'required', + }), + flag: z.boolean({ + required_error: 'required', + }), + }), + b: z + .object({ + text: z.string({ + required_error: 'required', + }), + flag: z.boolean({ + required_error: 'required', + }), + }) + .optional(), + }); + + expect(parseWithZod(createFormData([]), { schema })).toEqual({ + status: 'error', + payload: {}, + error: { + 'a.text': ['required'], + 'a.flag': ['required'], + }, + reply: expect.any(Function), + }); + expect( + parseWithZod(createFormData([['b.text', '']]), { schema }), + ).toEqual({ + status: 'error', + payload: { + b: { + text: '', + }, + }, + error: { + 'a.text': ['required'], + 'a.flag': ['required'], + 'b.text': ['required'], + 'b.flag': ['required'], + }, + reply: expect.any(Function), + }); + }); + test('z.array', () => { const createSchema = ( element: z.ZodTypeAny = z.string({