Skip to content

Commit

Permalink
Fix: Do not use instanceof
Browse files Browse the repository at this point in the history
  • Loading branch information
cau777 committed Jul 11, 2024
1 parent 8a2595a commit 495581b
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 9 deletions.
30 changes: 22 additions & 8 deletions src/zod-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,24 @@ export const parseFieldsAsArrays = <T extends Record<string, unknown>>(
) 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 = <T extends ZodTypeAny>(
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) {
Expand All @@ -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
Expand All @@ -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<any>)) {
return ZodTuple.create(
schema.items.map((item: any) => zodDeepPartial(item))
)
Expand Down
44 changes: 43 additions & 1 deletion test/zod-utils.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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(),
Expand Down

0 comments on commit 495581b

Please sign in to comment.