Skip to content

Commit 362eb33

Browse files
committed
Fix optional + pipe handling. Closes #5002. v4.0.13
1 parent 73a1970 commit 362eb33

File tree

6 files changed

+36
-4
lines changed

6 files changed

+36
-4
lines changed

packages/zod/jsr.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@zod/zod",
3-
"version": "4.0.12",
3+
"version": "4.0.13",
44
"exports": {
55
"./package.json": "./package.json",
66
".": "./src/index.ts",

packages/zod/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "zod",
3-
"version": "4.0.12",
3+
"version": "4.0.13",
44
"type": "module",
55
"license": "MIT",
66
"author": "Colin McDonnell <[email protected]>",

packages/zod/src/v4/classic/tests/optional.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,16 @@ test("pipe optionality inside objects", () => {
121121
e: string;
122122
}>();
123123
});
124+
125+
test("optional prop with pipe", () => {
126+
const schema = z.object({
127+
id: z
128+
.union([z.number(), z.string().nullish()])
129+
.transform((val) => (val === null || val === undefined ? val : Number(val)))
130+
.pipe(z.number())
131+
.optional(),
132+
});
133+
134+
schema.parse({});
135+
schema.parse({}, { jitless: true });
136+
});

packages/zod/src/v4/core/schemas.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3007,6 +3007,13 @@ export interface $ZodOptional<T extends SomeType = $ZodType> extends $ZodType {
30073007
_zod: $ZodOptionalInternals<T>;
30083008
}
30093009

3010+
function handleOptionalResult(result: ParsePayload, input: unknown) {
3011+
if (result.issues.length && input === undefined) {
3012+
return { issues: [], value: undefined };
3013+
}
3014+
return result;
3015+
}
3016+
30103017
export const $ZodOptional: core.$constructor<$ZodOptional> = /*@__PURE__*/ core.$constructor(
30113018
"$ZodOptional",
30123019
(inst, def) => {
@@ -3024,7 +3031,9 @@ export const $ZodOptional: core.$constructor<$ZodOptional> = /*@__PURE__*/ core.
30243031

30253032
inst._zod.parse = (payload, ctx) => {
30263033
if (def.innerType._zod.optin === "optional") {
3027-
return def.innerType._zod.run(payload, ctx);
3034+
const result = def.innerType._zod.run(payload, ctx);
3035+
if (result instanceof Promise) return result.then((r) => handleOptionalResult(r, payload.value));
3036+
return handleOptionalResult(result, payload.value);
30283037
}
30293038
if (payload.value === undefined) {
30303039
return payload;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export const version = {
22
major: 4,
33
minor: 0,
4-
patch: 12 as number,
4+
patch: 13 as number,
55
} as const;

play.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
11
import * as z from "zod";
22

33
z;
4+
5+
const schema = z.object({
6+
id: z
7+
.union([z.number(), z.string().nullish()])
8+
.transform((val) => (val === null || val === undefined ? val : Number(val)))
9+
.pipe(z.number())
10+
.optional(),
11+
});
12+
13+
console.log(schema.safeParse({}, { jitless: true }));

0 commit comments

Comments
 (0)