Skip to content

Instantly share code, notes, and snippets.

@flofehrenbacher
Created February 23, 2022 11:09
Show Gist options
  • Save flofehrenbacher/71f93f7a423cfc11e1dd018325c241ec to your computer and use it in GitHub Desktop.
Save flofehrenbacher/71f93f7a423cfc11e1dd018325c241ec to your computer and use it in GitHub Desktop.
import { z, ZodTypeAny, ZodUnion } from 'zod'
/**
* Zod helper for parsing arrays and ignore items not specified in the schema
*
* @param zodUnion - union of known types
*
* @example
* const binaryArraySchema = arrayIgnoreUnknown(z.union([z.literal('0'), z.literal('1')]))
* type BinaryArray = z.TypeOf<typeof binaryArraySchema>
*
* const binaryArray: BinaryArray = binaryArraySchema.parse(['0', '1', '2', '0'])
* console.log(binaryArray) // ['0', '1', '0']
*/
export function zodArrayIgnoreUnknown<T extends [ZodTypeAny, ...ZodTypeAny[]]>(
zodUnion: ZodUnion<T>,
) {
const isKnownItem = (item: unknown) => zodUnion.safeParse(item).success
return z.preprocess((val) => toSafeArray(val).filter(isKnownItem), z.array(zodUnion))
}
function toSafeArray(item: unknown): Array<unknown> {
if (isArray(item)) {
return item
}
return [item]
}
function isArray<T>(item: unknown): item is Array<T> {
return Array.isArray(item)
}
@flofehrenbacher
Copy link
Author

@tintin10q If I remember correctly the ZodUnion requires at least two inputs. Because of that I used the same constraint

@vimutti77
Copy link

vimutti77 commented Jun 21, 2023

Thanks, the preprocess solution is working with recursive schema.

function makeFilteredArraySchema<T extends ZodSchema>(schema: T) {
  return z.preprocess((val) => {
    const array = Array.isArray(val) ? val : [val]
    return array.filter((item: unknown) => schema.safeParse(item).success)
  }, z.array(schema))
}

Usage

const baseCategorySchema = z.object({
  name: z.string(),
})

type Category = z.infer<typeof baseCategorySchema> & {
  subcategories: Category[]
}

const categorySchema: z.ZodType<Category, z.ZodTypeDef, unknown> = baseCategorySchema.extend({
  subcategories: z.lazy(() => makeFilteredArraySchema(categorySchema)),
})

categorySchema.parse({
  name: 'People',
  subcategories: [
    {
      name: 'Politicians',
      subcategories: [
        {
          name: 'Presidents',
          subcategories: [],
        },
        {
          name: 123,
          subcategories: [],
        },
      ],
    },
    {
      name: 456,
      subcategories: [],
    },
  ],
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment