diff --git a/README.md b/README.md index c8933267d..b0f19fed7 100644 --- a/README.md +++ b/README.md @@ -1605,7 +1605,28 @@ const stringToNumber = z.string().transform((val) => myString.length); stringToNumber.parse("string"); // => 6 ``` -> ⚠️ Transform functions must not throw. Make sure to use refinements before the transform to make sure the input can be parsed by the transform. +> ⚠️ Transform functions must not throw. Make sure to use refinements before the transform or addIssue within the transform to make sure the input can be parsed by the transform. + +#### Validating during transform + +Similar to `superRefine`, `transform` can optionally take a `ctx`. This allows you to simultaneously +validate and transform the value, which can be simpler than chaining `refine` and `validate`. +When calling `ctx.addIssue` make sure to still return a value of the correct type otherwise the inferred type will include `undefined`. + +```ts +const Strings = z + .string() + .transform((val, ctx) => { + const parsed = parseInt(val); + if (isNaN(parsed)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Not a number", + }); + } + return parsed; + }); +``` #### Chaining order diff --git a/deno/lib/__tests__/transformer.test.ts b/deno/lib/__tests__/transformer.test.ts index fb1e48c34..bb513b117 100644 --- a/deno/lib/__tests__/transformer.test.ts +++ b/deno/lib/__tests__/transformer.test.ts @@ -11,7 +11,7 @@ const stringToNumber = z.string().transform((arg) => parseFloat(arg)); // .transform((n) => String(n)); const asyncNumberToString = z.number().transform(async (n) => String(n)); -test("transform ctx.addIssue", () => { +test("transform ctx.addIssue with parse", () => { const strs = ["foo", "bar"]; expect(() => { @@ -42,6 +42,38 @@ test("transform ctx.addIssue", () => { ); }); +test("transform ctx.addIssue with parseAsync", async () => { + const strs = ["foo", "bar"]; + + const result = await z + .string() + .transform((data, ctx) => { + const i = strs.indexOf(data); + if (i === -1) { + ctx.addIssue({ + code: "custom", + message: `${data} is not one of our allowed strings`, + }); + } + return data.length; + }) + .safeParseAsync("asdf"); + + expect(JSON.parse(JSON.stringify(result))).toEqual({ + success: false, + error: { + issues: [ + { + code: "custom", + message: "asdf is not one of our allowed strings", + path: [], + }, + ], + name: "ZodError", + }, + }); +}); + test("basic transformations", () => { const r1 = z .string() diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 0c3c160b8..1c807d360 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -3328,7 +3328,7 @@ export class ZodEffects< // return { status: "dirty", value: base.value }; // } return Promise.resolve(effect.transform(base.value, checkCtx)).then( - OK + (result) => ({ status: status.value, value: result }) ); }); } diff --git a/src/__tests__/transformer.test.ts b/src/__tests__/transformer.test.ts index 725802365..8686f032b 100644 --- a/src/__tests__/transformer.test.ts +++ b/src/__tests__/transformer.test.ts @@ -10,7 +10,7 @@ const stringToNumber = z.string().transform((arg) => parseFloat(arg)); // .transform((n) => String(n)); const asyncNumberToString = z.number().transform(async (n) => String(n)); -test("transform ctx.addIssue", () => { +test("transform ctx.addIssue with parse", () => { const strs = ["foo", "bar"]; expect(() => { @@ -41,6 +41,38 @@ test("transform ctx.addIssue", () => { ); }); +test("transform ctx.addIssue with parseAsync", async () => { + const strs = ["foo", "bar"]; + + const result = await z + .string() + .transform((data, ctx) => { + const i = strs.indexOf(data); + if (i === -1) { + ctx.addIssue({ + code: "custom", + message: `${data} is not one of our allowed strings`, + }); + } + return data.length; + }) + .safeParseAsync("asdf"); + + expect(JSON.parse(JSON.stringify(result))).toEqual({ + success: false, + error: { + issues: [ + { + code: "custom", + message: "asdf is not one of our allowed strings", + path: [], + }, + ], + name: "ZodError", + }, + }); +}); + test("basic transformations", () => { const r1 = z .string() diff --git a/src/types.ts b/src/types.ts index 8d4018e75..7fc741788 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3328,7 +3328,7 @@ export class ZodEffects< // return { status: "dirty", value: base.value }; // } return Promise.resolve(effect.transform(base.value, checkCtx)).then( - OK + (result) => ({ status: status.value, value: result }) ); }); }