typescriptzod

How to iterate over properties and get the types in Zod?


Say you have a basic system like this in Zod:

import { z } from 'zod'

const Post = z.object({
  title: z.string(),
})

const User = z.object({
  name: z.string(),
  email: z.string().optional(),
  posts: z.array(Post),
  loginCount: z.number().min(1)
})

How do you iterate through the properties and inspect the options on the properties?

I see there is a .shape property, but how do you do this:

for (const name in shape) {
  const prop = shape[name]

  if (prop.min != null) {
    console.log(`Has min ${prop.min}`)
  }
  if (prop.max != null) {
    console.log(`Has max ${prop.max}`);
  }
  if (prop.optional === true) {
    console.log(`Is optional`);
  }
  if (prop.type) {
    if (prop.type is array) {
      prop.type.forEach(item => {
        console.log(`Has array item type ${item.name}`)
      })
    } else if (prop.type is union) {
      // ... show union type names
    } else {
      // show basic type name
    }
  }
}

I need to use this definition to, for example:

I don't see any docs for stuff like this and don't want to end up reinventing the wheel by making my own library to do types and type inspection.


Solution

  • Zod isn't really made to be used like this, but depending on the complexity of your schemas, you can come quite far by looking at the source: https://github.com/colinhacks/zod/blob/master/src/types.ts

    For example, you can see that the various types are classes, and for example that the number type has minValue and maxValue getters. So to get those, you can do:

    if (s instanceof ZodNumber)
      console.log(s.minValue, s.maxValue)
    

    The shape you mention is a getter on the ZodObject type, which means you can do:

    if (s instanceof ZodObject)
      for (const field of schema.shape)
        // look at each field
    

    You'll also find that optional and nullable schemas are the classes ZodOptional and ZodNullable, and that they both have an unwrap() method to look at the inner values, which means you could do something like this:

    let required = true;
    
    while (s instanceof ZodOptional || s instanceof ZodNullable) {
      required = false;
      s = s.unwrap();
    }
    

    If you do refinements or transformations, those will be wrapped in the ZodEffects class, and there you'll see there's a innerType() method to get to the inner type there.

    Basically, you just need to recursively loop through things, look at each thing, look at what type it is, and deal with it according to your need.