Skip to content

Commit 4d43ff0

Browse files
committed
fix: replace es2019 class fields with local members to address #3611
1 parent f7ad261 commit 4d43ff0

File tree

4 files changed

+144
-24
lines changed

4 files changed

+144
-24
lines changed

deno/lib/__tests__/enum.test.ts

+59
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,62 @@ test("readonly in ZodEnumDef", () => {
8888
let _t!: z.ZodEnumDef<readonly ["a", "b"]>;
8989
_t;
9090
});
91+
92+
test("enum parsing works after cloning", () => {
93+
function deepClone(value: any) {
94+
// Handle null and undefined
95+
if (value == null) {
96+
return value;
97+
}
98+
99+
// Get the constructor and prototype
100+
const constructor = Object.getPrototypeOf(value).constructor;
101+
102+
// Handle primitive wrappers
103+
if ([Boolean, Number, String].includes(constructor)) {
104+
return new constructor(value);
105+
}
106+
107+
// Handle Date objects
108+
if (constructor === Date) {
109+
return new Date(value.getTime());
110+
}
111+
112+
// Handle Arrays
113+
if (constructor === Array) {
114+
return value.map((item: any) => deepClone(item));
115+
}
116+
117+
// Handle basic RegExp
118+
if (constructor === RegExp) {
119+
return new RegExp(value.source, value.flags);
120+
}
121+
122+
// Handle Objects (including custom classes)
123+
if (typeof value === 'object') {
124+
// Create new instance while preserving the prototype chain
125+
const cloned = Object.create(Object.getPrototypeOf(value));
126+
127+
// Clone own properties
128+
const descriptors = Object.getOwnPropertyDescriptors(value);
129+
for (const [key, descriptor] of Object.entries(descriptors)) {
130+
if (descriptor.value !== undefined) {
131+
descriptor.value = deepClone(descriptor.value);
132+
}
133+
Object.defineProperty(cloned, key, descriptor);
134+
}
135+
136+
return cloned;
137+
}
138+
139+
// Return primitives and functions as is
140+
return value;
141+
}
142+
143+
const schema = {
144+
mood: z.enum(["happy", "sad", "neutral", "feisty"]),
145+
};
146+
z.object(schema).safeParse({ mood: "feisty" }); //Sanity check before cloning
147+
const clonedDeep2 = deepClone(schema);
148+
z.object(clonedDeep2).safeParse({ mood: "feisty" });
149+
});

deno/lib/types.ts

+13-12
Original file line numberDiff line numberDiff line change
@@ -4342,14 +4342,20 @@ function createZodEnum(
43424342
});
43434343
}
43444344

4345+
const lookupSymbol = Symbol("lookup");
43454346
export class ZodEnum<T extends [string, ...string[]]> extends ZodType<
43464347
T[number],
43474348
ZodEnumDef<T>,
43484349
T[number]
43494350
> {
4350-
#cache: Set<T[number]> | undefined;
4351-
4351+
private [lookupSymbol]: Set<T[number]> | undefined;
43524352
_parse(input: ParseInput): ParseReturnType<this["_output"]> {
4353+
let lookup = this[lookupSymbol];
4354+
if (!lookup) {
4355+
console.log("setting lookup");
4356+
lookup = new Set(this._def.values);
4357+
this[lookupSymbol] = lookup;
4358+
}
43534359
if (typeof input.data !== "string") {
43544360
const ctx = this._getOrReturnCtx(input);
43554361
const expectedValues = this._def.values;
@@ -4361,14 +4367,9 @@ export class ZodEnum<T extends [string, ...string[]]> extends ZodType<
43614367
return INVALID;
43624368
}
43634369

4364-
if (!this.#cache) {
4365-
this.#cache = new Set(this._def.values);
4366-
}
4367-
4368-
if (!this.#cache.has(input.data)) {
4370+
if (!lookup.has(input.data)) {
43694371
const ctx = this._getOrReturnCtx(input);
43704372
const expectedValues = this._def.values;
4371-
43724373
addIssueToContext(ctx, {
43734374
received: ctx.data,
43744375
code: ZodIssueCode.invalid_enum_value,
@@ -4458,7 +4459,7 @@ export class ZodNativeEnum<T extends EnumLike> extends ZodType<
44584459
ZodNativeEnumDef<T>,
44594460
T[keyof T]
44604461
> {
4461-
#cache: Set<T[keyof T]> | undefined;
4462+
private [lookupSymbol]: Set<T[keyof T]> | undefined;
44624463
_parse(input: ParseInput): ParseReturnType<T[keyof T]> {
44634464
const nativeEnumValues = util.getValidEnumValues(this._def.values);
44644465

@@ -4476,11 +4477,11 @@ export class ZodNativeEnum<T extends EnumLike> extends ZodType<
44764477
return INVALID;
44774478
}
44784479

4479-
if (!this.#cache) {
4480-
this.#cache = new Set(util.getValidEnumValues(this._def.values));
4480+
if (!this[lookupSymbol]) {
4481+
this[lookupSymbol] = new Set(util.getValidEnumValues(this._def.values));
44814482
}
44824483

4483-
if (!this.#cache.has(input.data)) {
4484+
if (!this[lookupSymbol].has(input.data)) {
44844485
const expectedValues = util.objectValues(nativeEnumValues);
44854486

44864487
addIssueToContext(ctx, {

src/__tests__/enum.test.ts

+59
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,62 @@ test("readonly in ZodEnumDef", () => {
8787
let _t!: z.ZodEnumDef<readonly ["a", "b"]>;
8888
_t;
8989
});
90+
91+
test("enum parsing works after cloning", () => {
92+
function deepClone(value: any) {
93+
// Handle null and undefined
94+
if (value == null) {
95+
return value;
96+
}
97+
98+
// Get the constructor and prototype
99+
const constructor = Object.getPrototypeOf(value).constructor;
100+
101+
// Handle primitive wrappers
102+
if ([Boolean, Number, String].includes(constructor)) {
103+
return new constructor(value);
104+
}
105+
106+
// Handle Date objects
107+
if (constructor === Date) {
108+
return new Date(value.getTime());
109+
}
110+
111+
// Handle Arrays
112+
if (constructor === Array) {
113+
return value.map((item: any) => deepClone(item));
114+
}
115+
116+
// Handle basic RegExp
117+
if (constructor === RegExp) {
118+
return new RegExp(value.source, value.flags);
119+
}
120+
121+
// Handle Objects (including custom classes)
122+
if (typeof value === 'object') {
123+
// Create new instance while preserving the prototype chain
124+
const cloned = Object.create(Object.getPrototypeOf(value));
125+
126+
// Clone own properties
127+
const descriptors = Object.getOwnPropertyDescriptors(value);
128+
for (const [key, descriptor] of Object.entries(descriptors)) {
129+
if (descriptor.value !== undefined) {
130+
descriptor.value = deepClone(descriptor.value);
131+
}
132+
Object.defineProperty(cloned, key, descriptor);
133+
}
134+
135+
return cloned;
136+
}
137+
138+
// Return primitives and functions as is
139+
return value;
140+
}
141+
142+
const schema = {
143+
mood: z.enum(["happy", "sad", "neutral", "feisty"]),
144+
};
145+
z.object(schema).safeParse({ mood: "feisty" }); //Sanity check before cloning
146+
const clonedDeep2 = deepClone(schema);
147+
z.object(clonedDeep2).safeParse({ mood: "feisty" });
148+
});

src/types.ts

+13-12
Original file line numberDiff line numberDiff line change
@@ -4342,14 +4342,20 @@ function createZodEnum(
43424342
});
43434343
}
43444344

4345+
const lookupSymbol = Symbol("lookup");
43454346
export class ZodEnum<T extends [string, ...string[]]> extends ZodType<
43464347
T[number],
43474348
ZodEnumDef<T>,
43484349
T[number]
43494350
> {
4350-
#cache: Set<T[number]> | undefined;
4351-
4351+
private [lookupSymbol]: Set<T[number]> | undefined;
43524352
_parse(input: ParseInput): ParseReturnType<this["_output"]> {
4353+
let lookup = this[lookupSymbol];
4354+
if (!lookup) {
4355+
console.log("setting lookup");
4356+
lookup = new Set(this._def.values);
4357+
this[lookupSymbol] = lookup;
4358+
}
43534359
if (typeof input.data !== "string") {
43544360
const ctx = this._getOrReturnCtx(input);
43554361
const expectedValues = this._def.values;
@@ -4361,14 +4367,9 @@ export class ZodEnum<T extends [string, ...string[]]> extends ZodType<
43614367
return INVALID;
43624368
}
43634369

4364-
if (!this.#cache) {
4365-
this.#cache = new Set(this._def.values);
4366-
}
4367-
4368-
if (!this.#cache.has(input.data)) {
4370+
if (!lookup.has(input.data)) {
43694371
const ctx = this._getOrReturnCtx(input);
43704372
const expectedValues = this._def.values;
4371-
43724373
addIssueToContext(ctx, {
43734374
received: ctx.data,
43744375
code: ZodIssueCode.invalid_enum_value,
@@ -4458,7 +4459,7 @@ export class ZodNativeEnum<T extends EnumLike> extends ZodType<
44584459
ZodNativeEnumDef<T>,
44594460
T[keyof T]
44604461
> {
4461-
#cache: Set<T[keyof T]> | undefined;
4462+
private [lookupSymbol]: Set<T[keyof T]> | undefined;
44624463
_parse(input: ParseInput): ParseReturnType<T[keyof T]> {
44634464
const nativeEnumValues = util.getValidEnumValues(this._def.values);
44644465

@@ -4476,11 +4477,11 @@ export class ZodNativeEnum<T extends EnumLike> extends ZodType<
44764477
return INVALID;
44774478
}
44784479

4479-
if (!this.#cache) {
4480-
this.#cache = new Set(util.getValidEnumValues(this._def.values));
4480+
if (!this[lookupSymbol]) {
4481+
this[lookupSymbol] = new Set(util.getValidEnumValues(this._def.values));
44814482
}
44824483

4483-
if (!this.#cache.has(input.data)) {
4484+
if (!this[lookupSymbol].has(input.data)) {
44844485
const expectedValues = util.objectValues(nativeEnumValues);
44854486

44864487
addIssueToContext(ctx, {

0 commit comments

Comments
 (0)