From 495581b39f9d18736293445b7f71afd7a3b7e63b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=A3=20Rinaldi?= Date: Thu, 11 Jul 2024 10:51:03 -0300 Subject: [PATCH] Fix: Do not use instanceof --- src/zod-utils.ts | 30 ++++++++++++++++++++-------- test/zod-utils.test.ts | 44 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/zod-utils.ts b/src/zod-utils.ts index abd35a2..eef3dfa 100644 --- a/src/zod-utils.ts +++ b/src/zod-utils.ts @@ -35,10 +35,24 @@ export const parseFieldsAsArrays = >( ) as T } +/** + * We cannot use the instanceof operator because it will always return + * false when the library is called in a WebPack bundle (but not on tests). + * https://stackoverflow.com/questions/59265098/instanceof-not-work-correctly-in-typescript-library-project + * @param obj + * @param schema + */ +export const isInstanceOfSchema = ( + obj: ZodTypeAny, + schema: { new (...args: any): T } +): obj is T => { + return obj._def.typeName === schema.name +} + // Copied from github.com/colinhacks/zod/blob/6dad90785398885f7b058f5c0760d5ae5476b833/src/types.ts#L2189-L2217 // and extended to support unions and discriminated unions export const zodDeepPartial = (schema: ZodTypeAny): ZodTypeAny => { - if (schema instanceof ZodObject) { + if (isInstanceOfSchema(schema, ZodObject)) { const newShape: any = {} for (const key in schema.shape) { @@ -49,18 +63,18 @@ export const zodDeepPartial = (schema: ZodTypeAny): ZodTypeAny => { ...schema._def, shape: () => newShape, }) as any - } else if (schema instanceof ZodIntersection) { + } else if (isInstanceOfSchema(schema, ZodIntersection)) { return ZodIntersection.create( zodDeepPartial(schema._def.left), zodDeepPartial(schema._def.right) ) - } else if (schema instanceof ZodUnion) { + } else if (isInstanceOfSchema(schema, ZodUnion)) { type Options = [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]] const options = schema._def.options as Options return ZodUnion.create( options.map((option) => zodDeepPartial(option)) as Options ) as any - } else if (schema instanceof ZodDiscriminatedUnion) { + } else if (isInstanceOfSchema(schema, ZodDiscriminatedUnion)) { const types = Object.values(schema.options) as (AnyZodObject & ZodRawShape)[] const discriminator = schema.discriminator @@ -81,16 +95,16 @@ export const zodDeepPartial = (schema: ZodTypeAny): ZodTypeAny => { } as any) }) as any return ZodDiscriminatedUnion.create(discriminator, newTypes) as any - } else if (schema instanceof ZodArray) { + } else if (isInstanceOfSchema(schema, ZodArray)) { return new ZodArray({ ...schema._def, type: zodDeepPartial(schema.element), }) - } else if (schema instanceof ZodOptional) { + } else if (isInstanceOfSchema(schema, ZodOptional)) { return ZodOptional.create(zodDeepPartial(schema.unwrap())) - } else if (schema instanceof ZodNullable) { + } else if (isInstanceOfSchema(schema, ZodNullable)) { return ZodNullable.create(zodDeepPartial(schema.unwrap())) - } else if (schema instanceof ZodTuple) { + } else if (isInstanceOfSchema(schema, ZodTuple)) { return ZodTuple.create( schema.items.map((item: any) => zodDeepPartial(item)) ) diff --git a/test/zod-utils.test.ts b/test/zod-utils.test.ts index 515ed2f..37c80f1 100644 --- a/test/zod-utils.test.ts +++ b/test/zod-utils.test.ts @@ -1,5 +1,9 @@ import * as z from 'zod' -import { zodDeepPartial, parseFieldsAsArrays } from '../src/zod-utils' +import { + zodDeepPartial, + parseFieldsAsArrays, + isInstanceOfSchema, +} from '../src/zod-utils' describe('zodDeepPartial', () => { test('partial of primitives', () => { @@ -82,6 +86,44 @@ describe('zodDeepPartial', () => { }) }) +describe('isInstanceOfSchema', () => { + test('should return true for same schema', () => { + expect(isInstanceOfSchema(z.object({}), z.ZodObject)).toBeTruthy() + expect( + isInstanceOfSchema(z.object({ prop: z.number() }), z.ZodObject) + ).toBeTruthy() + expect(isInstanceOfSchema(z.literal('TEST'), z.ZodLiteral)).toBeTruthy() + expect( + isInstanceOfSchema(z.union([z.number(), z.string()]), z.ZodUnion) + ).toBeTruthy() + expect( + isInstanceOfSchema( + z.intersection(z.literal('TEST'), z.string()), + z.ZodIntersection + ) + ).toBeTruthy() + expect(isInstanceOfSchema(z.ZodNumber.create(), z.ZodNumber)).toBeTruthy() + }) + test('should return false for different schemas', () => { + expect(isInstanceOfSchema(z.string(), z.ZodObject)).toBeFalsy() + expect( + isInstanceOfSchema(z.object({ prop: z.number() }), z.ZodNumber) + ).toBeFalsy() + expect(isInstanceOfSchema(z.number().optional(), z.ZodNumber)).toBeFalsy() + expect(isInstanceOfSchema(z.number().nullable(), z.ZodOptional)).toBeFalsy() + expect(isInstanceOfSchema(z.literal('TEST'), z.ZodString)).toBeFalsy() + expect( + isInstanceOfSchema( + z.discriminatedUnion('type', [ + z.object({ type: z.literal('a') }), + z.object({ type: z.literal('b') }), + ]), + z.ZodUnion + ) + ).toBeFalsy() + }) +}) + describe('parseFieldsAsArrays', () => { const schema = z.object({ strings: z.string().array(),