Skip to content

Commit 103f69b

Browse files
authored
Use output type for preprocess (#4552)
* Remove usage of private fields in Zod 3 * Abort further processing when transform is skipped * Use output type for preprocess
1 parent 2437ec0 commit 103f69b

File tree

5 files changed

+58
-13
lines changed

5 files changed

+58
-13
lines changed

packages/zod/src/v3/tests/refine.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,3 +295,19 @@ test("fatal superRefine", () => {
295295
expect(result.success).toEqual(false);
296296
if (!result.success) expect(result.error.issues.length).toEqual(1);
297297
});
298+
299+
test("superRefine after skipped transform", () => {
300+
const schema = z
301+
.string()
302+
.regex(/^\d+$/)
303+
.transform((val) => Number(val))
304+
.superRefine((val) => {
305+
if (typeof val !== "number") {
306+
throw new Error("Called without transform");
307+
}
308+
});
309+
310+
const result = schema.safeParse("");
311+
312+
expect(result.success).toEqual(false);
313+
});

packages/zod/src/v3/types.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4074,7 +4074,7 @@ function createZodEnum(values: [string, ...string[]], params?: RawCreateParams)
40744074
}
40754075

40764076
export class ZodEnum<T extends [string, ...string[]]> extends ZodType<T[number], ZodEnumDef<T>, T[number]> {
4077-
#cache: Set<T[number]> | undefined;
4077+
_cache: Set<T[number]> | undefined;
40784078

40794079
_parse(input: ParseInput): ParseReturnType<this["_output"]> {
40804080
if (typeof input.data !== "string") {
@@ -4088,11 +4088,11 @@ export class ZodEnum<T extends [string, ...string[]]> extends ZodType<T[number],
40884088
return INVALID;
40894089
}
40904090

4091-
if (!this.#cache) {
4092-
this.#cache = new Set(this._def.values);
4091+
if (!this._cache) {
4092+
this._cache = new Set(this._def.values);
40934093
}
40944094

4095-
if (!this.#cache.has(input.data)) {
4095+
if (!this._cache.has(input.data)) {
40964096
const ctx = this._getOrReturnCtx(input);
40974097
const expectedValues = this._def.values;
40984098

@@ -4172,7 +4172,7 @@ export interface ZodNativeEnumDef<T extends EnumLike = EnumLike> extends ZodType
41724172
export type EnumLike = { [k: string]: string | number; [nu: number]: string };
41734173

41744174
export class ZodNativeEnum<T extends EnumLike> extends ZodType<T[keyof T], ZodNativeEnumDef<T>, T[keyof T]> {
4175-
#cache: Set<T[keyof T]> | undefined;
4175+
_cache: Set<T[keyof T]> | undefined;
41764176
_parse(input: ParseInput): ParseReturnType<T[keyof T]> {
41774177
const nativeEnumValues = util.getValidEnumValues(this._def.values);
41784178

@@ -4187,11 +4187,11 @@ export class ZodNativeEnum<T extends EnumLike> extends ZodType<T[keyof T], ZodNa
41874187
return INVALID;
41884188
}
41894189

4190-
if (!this.#cache) {
4191-
this.#cache = new Set(util.getValidEnumValues(this._def.values));
4190+
if (!this._cache) {
4191+
this._cache = new Set(util.getValidEnumValues(this._def.values));
41924192
}
41934193

4194-
if (!this.#cache.has(input.data)) {
4194+
if (!this._cache.has(input.data)) {
41954195
const expectedValues = util.objectValues(nativeEnumValues);
41964196

41974197
addIssueToContext(ctx, {
@@ -4411,7 +4411,7 @@ export class ZodEffects<T extends ZodTypeAny, Output = output<T>, Input = input<
44114411
parent: ctx,
44124412
});
44134413

4414-
if (!isValid(base)) return base;
4414+
if (!isValid(base)) return INVALID;
44154415

44164416
const result = effect.transform(base.value, checkCtx);
44174417
if (result instanceof Promise) {
@@ -4423,7 +4423,7 @@ export class ZodEffects<T extends ZodTypeAny, Output = output<T>, Input = input<
44234423
return { status: status.value, value: result };
44244424
} else {
44254425
return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base) => {
4426-
if (!isValid(base)) return base;
4426+
if (!isValid(base)) return INVALID;
44274427

44284428
return Promise.resolve(effect.transform(base.value, checkCtx)).then((result) => ({
44294429
status: status.value,

packages/zod/src/v4/classic/tests/json-schema.test.ts renamed to packages/zod/src/v4/classic/tests/to-json-schema.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,3 +1943,14 @@ test("examples on pipe", () => {
19431943
// `);
19441944

19451945
// });
1946+
1947+
test("use output type for preprocess", () => {
1948+
const a = z.preprocess((val) => String(val), z.string());
1949+
1950+
expect(z.toJSONSchema(a, { io: "input" })).toMatchInlineSnapshot(`
1951+
{
1952+
"$schema": "https://json-schema.org/draft/2020-12/schema",
1953+
"type": "string",
1954+
}
1955+
`);
1956+
});

packages/zod/src/v4/core/to-json-schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ export class JSONSchemaGenerator {
499499
break;
500500
}
501501
case "pipe": {
502-
const innerType = this.io === "input" ? def.in : def.out;
502+
const innerType = this.io === "input" ? (def.in._zod.def.type === "transform" ? def.out : def.in) : def.out;
503503
this.process(innerType, params);
504504
result.ref = innerType;
505505
break;

play.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1-
import { z } from "zod/v4";
1+
import { z } from "zod/v3";
22

3-
z;
3+
// import * as z from "zod";
4+
5+
const parseResult = z
6+
.string()
7+
.refine((x) => false) // force anything/everything to fail
8+
.transform((x) => {
9+
console.log("I don't get called"); // correct
10+
return x.length;
11+
})
12+
.refine((x) => {
13+
console.log("I shouldn't get called!!!!!!");
14+
console.log(typeof x); // number
15+
console.log(x);
16+
})
17+
.safeParse("123");
18+
19+
console.log(`succeeded: ${parseResult.success}`); // false (correct behavior)
20+
21+
// z.string().min(1234, { abort: true });

0 commit comments

Comments
 (0)