Repository: colinhacks/zod
Pull request #4553 (2025-05-28 22:21:48)

Use output type for preprocess


Patch:
diff --git a/packages/zod/src/v3/types.ts b/packages/zod/src/v3/types.ts
index 2b3efe02..82d66375 100644
--- a/packages/zod/src/v3/types.ts
+++ b/packages/zod/src/v3/types.ts
@@ -4411,7 +4411,7 @@ export class ZodEffects<T extends ZodTypeAny, Output = output<T>, Input = input<
           parent: ctx,
         });
 
-        if (!isValid(base)) return base;
+        if (!isValid(base)) return INVALID;
 
         const result = effect.transform(base.value, checkCtx);
         if (result instanceof Promise) {
@@ -4423,7 +4423,7 @@ export class ZodEffects<T extends ZodTypeAny, Output = output<T>, Input = input<
         return { status: status.value, value: result };
       } else {
         return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base) => {
-          if (!isValid(base)) return base;
+          if (!isValid(base)) return INVALID;
 
           return Promise.resolve(effect.transform(base.value, checkCtx)).then((result) => ({
             status: status.value,
diff --git a/packages/zod/src/v4/core/to-json-schema.ts b/packages/zod/src/v4/core/to-json-schema.ts
index 06eeb793..641c7943 100644
--- a/packages/zod/src/v4/core/to-json-schema.ts
+++ b/packages/zod/src/v4/core/to-json-schema.ts
@@ -499,7 +499,7 @@ export class JSONSchemaGenerator {
           break;
         }
         case "pipe": {
-          const innerType = this.io === "input" ? def.in : def.out;
+          const innerType = this.io === "input" ? (def.in._zod.def.type === "transform" ? def.out : def.in) : def.out;
           this.process(innerType, params);
           result.ref = innerType;
           break;
diff --git a/play.ts b/play.ts
index 41eb006b..23bdd10a 100644
--- a/play.ts
+++ b/play.ts
@@ -1,3 +1,21 @@
-import { z } from "zod/v4";
+import { z } from "zod/v3";
 
-z;
+// import * as z from "zod";
+
+const parseResult = z
+  .string()
+  .refine((x) => false) // force anything/everything to fail
+  .transform((x) => {
+    console.log("I don't get called"); // correct
+    return x.length;
+  })
+  .refine((x) => {
+    console.log("I shouldn't get called!!!!!!");
+    console.log(typeof x); // number
+    console.log(x);
+  })
+  .safeParse("123");
+
+console.log(`succeeded:  ${parseResult.success}`); // false (correct behavior)
+
+// z.string().min(1234, { abort: true });


Test patch:
diff --git a/packages/zod/src/v3/tests/refine.test.ts b/packages/zod/src/v3/tests/refine.test.ts
index 8a7165cd..55c27fea 100644
--- a/packages/zod/src/v3/tests/refine.test.ts
+++ b/packages/zod/src/v3/tests/refine.test.ts
@@ -295,3 +295,19 @@ test("fatal superRefine", () => {
   expect(result.success).toEqual(false);
   if (!result.success) expect(result.error.issues.length).toEqual(1);
 });
+
+test("superRefine after skipped transform", () => {
+  const schema = z
+    .string()
+    .regex(/^\d+$/)
+    .transform((val) => Number(val))
+    .superRefine((val) => {
+      if (typeof val !== "number") {
+        throw new Error("Called without transform");
+      }
+    });
+
+  const result = schema.safeParse("");
+
+  expect(result.success).toEqual(false);
+});
diff --git a/packages/zod/src/v4/classic/tests/json-schema.test.ts b/packages/zod/src/v4/classic/tests/to-json-schema.test.ts
similarity index 99%
rename from packages/zod/src/v4/classic/tests/json-schema.test.ts
rename to packages/zod/src/v4/classic/tests/to-json-schema.test.ts
index d014ddf2..9aeac330 100644
--- a/packages/zod/src/v4/classic/tests/json-schema.test.ts
+++ b/packages/zod/src/v4/classic/tests/to-json-schema.test.ts
@@ -1943,3 +1943,14 @@ test("examples on pipe", () => {
 //   `);
 
 // });
+
+test("use output type for preprocess", () => {
+  const a = z.preprocess((val) => String(val), z.string());
+
+  expect(z.toJSONSchema(a, { io: "input" })).toMatchInlineSnapshot(`
+    {
+      "$schema": "https://json-schema.org/draft/2020-12/schema",
+      "type": "string",
+    }
+  `);
+});