• Stars
    star
    342
  • Rank 123,697 (Top 3 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created about 3 years ago
  • Updated about 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

generate TypeScript types from your Zod schema

zod-to-ts

generate TypeScript types from your Zod schema

Installation

npm install zod-to-ts zod typescript

You must be on zod@3 and typescript@4.

Usage

import { z } from 'zod'
import { zodToTs } from 'zod-to-ts'

// define your Zod schema
const UserSchema = z.object({
	username: z.string(),
	age: z.number(),
	inventory: z.object({
		name: z.string(),
		itemId: z.number(),
	}).array(),
})

// pass schema and name of type/identifier
const { node } = zodToTs(UserSchema, 'User')

result:

{
  username: string
  age: number
  inventory: {
    name: string
    itemId: number
  }[]
}

You must pass in the identifier User or it will default to Identifier. This is necessary to handle cases like recursive types and native enums. zodToTs() only returns the type value, not the actual type declaration. If you want to add an identifier to the type and create a type declaration, you can use the createTypeAlias() utility:

import { createTypeAlias, zodToTs } from 'zod-to-ts'

const identifier = 'User'
const { node } = zodToTs(UserSchema, identifier)
const typeAlias = createTypeAlias(
	node,
	identifier,
	// optionally pass a comment
	// comment: UserSchema.description
)

result:

type User = {
	username: string
}

zodToTs() and createTypeAlias() return a TS AST nodes, so if you want to get the node as a string, you can use the printNode() utility.

zodToTs():

import { printNode, zodToTs } from 'zod-to-ts'

const identifier = 'User'
const { node } = zodToTs(UserSchema, identifier)
const nodeString = printNode(node)

result:

"{
  username: string
  age: number
  inventory: {
    name: string
    itemId: number
  }[]
}"

createTypeAlias():

import { createTypeAlias, printNode, zodToTs } from 'zod-to-ts'

const identifier = 'User'
const { node } = zodToTs(UserSchema, identifier)
const typeAlias = createTypeAlias(node, identifier)
const nodeString = printNode(typeAlias)

result:

"type User = {
  username: string
  age: number
  inventory: {
    name: string
    itemId: number
  }[]
}"

Overriding Types

You can use withGetType to override a type, which is useful when more information is needed to determine the actual type. Unfortunately, this means working with the TS AST:

import { z } from 'zod'
import { withGetType, zodToTs } from 'zod-to-ts'

const DateSchema = withGetType(
	z.instanceof(Date),
	(ts) => ts.factory.createIdentifier('Date'),
)

const ItemSchema = z.object({
	name: z.string(),
	date: DateSchema,
})

const { node } = zodToTs(ItemSchema, 'Item')

result without withGetType override:

type Item = {
	name: string
	date: any
}

result with override:

type Item = {
	name: string
	date: Date
}

TypeScript AST Viewer can help a lot with this if you are having trouble referencing something. It even provides copy-pastable code!

Special Cases

z.lazy()

Lazy types default to referencing the root type (User in the following example). It is impossible to determine what it is referencing otherwise.

// Zod cannot infer types when you use the z.lazy
// so you must define it
import { z } from 'zod'
type User = {
	username: string
	friends: User[]
}

const UserSchema: z.ZodSchema<User> = z.object({
	username: z.string(),
	friends: z.lazy(() => UserSchema).array(),
})

const { node } = zodToTs(UserSchema, 'User')

result:

type User = {
	username: string
	friends: User[]
}

But what happens when the schema looks like this?

type User = {
	username: string
	item: {
		name: string
		itemId: string
	}
	friends: User[]
}

// essentially when you are referencing a different field
// and not the root type
const friendItems = z.lazy(() => UserSchema.item).array()

const UserSchema: z.ZodSchema<User> = z.object({
	username: z.string(),
	item: z.object({
		name: z.string(),
		id: z.number(),
	}),
	friendItems,
})

const { node } = zodToTs(UserSchema, 'User')

result:

{
  username: string
  item: {
    name: string
    id: number
  }
  friendItems: User[]
}

friendItems will still have the User type even though it is actually referencing UserSchema["item"]. You must provide the actual type using withGetType:

import { z } from 'zod'
import { withGetType } from 'zod-to-ts'
type User = {
	username: string
	item: {
		name: string
		id: number
	}
	friends: User[]
}

const friendItems: z.Schema<User['item'][]> = withGetType(
	z.lazy(() => UserSchema.item).array(),
	// return a TS AST node
	(ts, identifier) =>
		ts.factory.createIndexedAccessTypeNode(
			ts.factory.createTypeReferenceNode(
				ts.factory.createIdentifier(identifier),
				undefined,
			),
			ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral('item')),
		),
)

const UserSchema: z.ZodSchema<User> = z.object({
	username: z.string(),
	item: z.object({
		name: z.string(),
		id: z.number(),
	}),
	friendItems,
})

const { node } = zodToTs(UserSchema, 'User')

result:

{
  username: string
  item: {
    name: string
    id: number
  }
  friendItems: User['item'][]
}

z.nativeEnum()

z.enum() is always preferred, but sometimes z.nativeEnum() is necessary. z.nativeEnum() works similarly to z.lazy() in that the identifier of the enum cannot be determined:

import { z } from 'zod'
import { withGetType } from 'zod-to-ts'

enum Fruit {
  Apple = 'apple',
  Banana = 'banana',
  Cantaloupe = 'cantaloupe',
}

const fruitNativeEnum: = z.nativeEnum(
  Fruit,
)

const TreeSchema = z.object({
  fruit: fruitNativeEnum,
})

result:

{
  fruit: unknown
}

There are two ways to solve this: provide an identifier to it or resolve all the enums inside zodToTs().

Option 1 - providing an identifier using withGetType():

import { z } from 'zod'
import { withGetType, zodToTs } from 'zod-to-ts'

enum Fruit {
	Apple = 'apple',
	Banana = 'banana',
	Cantaloupe = 'cantaloupe',
}

const fruitNativeEnum = withGetType(
	z.nativeEnum(
		Fruit,
	),
	// return an identifier that will be used on the enum type
	(ts) => ts.factory.createIdentifier('Fruit'),
)

const TreeSchema = z.object({
	fruit: fruitNativeEnum,
})

const { node } = zodToTs(TreeSchema)

result:

{
  fruit: Fruit
}

Option 2 - resolve enums. This is the same as before, but you just need to pass an option:

const TreeTSType = zodToTs(TreeSchema, undefined, { resolveNativeEnums: true })

result:

{
  node: {
    fruit: Fruit
  },
  store: {
    nativeEnums: [
      enum Fruit {
        Apple = 'apple',
        Banana = 'banana',
        Cantaloupe = 'cantaloupe',
      }
    ]
  }
}

Note: These are not the actual values, they are TS representation. The actual values are TS AST nodes.

This option allows you to embed the enums before the schema without actually depending on an external enum type.

More Repositories

1

trpc-playground

playground for running tRPC queries in the browser
TypeScript
289
star
2

trpc-v10-migrate-codemod

codemod to migrate your tRPC codebase from v9 to v10
TypeScript
69
star
3

trpc-bun

tRPC + Bun
TypeScript
64
star
4

trpc-pokemon

public tRPC Pokemon API
TypeScript
53
star
5

ts-lib-starter

Boilerplate for your next TypeScript library. Build with speed.
TypeScript
50
star
6

rodemirror

React component for CodeMirror 6
TypeScript
49
star
7

uttp

write your request handlers once, run anywhere
TypeScript
36
star
8

onetyped

one schema to rule them all
TypeScript
25
star
9

trpc-ultra

tRPC + Ultra
TypeScript
21
star
10

simple-icons-py

python wrapper for simple-icons
Python
18
star
11

discordbotdash

A python package for testing/managing your discord.py bot in browser.
HTML
10
star
12

rehype-tree-sitter-highlight

highlight your code using tree-sitter-highlight
TypeScript
9
star
13

yarn-plugin-postinstall-dev

Run a postinstallDev script after installation
TypeScript
8
star
14

pdfc

PDF compiler for your source code
TypeScript
7
star
15

trpc-rakkas

tRPC in Rakkas
TypeScript
6
star
16

shiki-renderer-pdf

PDF renderer for Shiki
TypeScript
6
star
17

trpc-nextjs-middleware

tRPC + Next.js Middleware
TypeScript
5
star
18

eslint-config

my ESLint config
TypeScript
4
star
19

si-components

simple-icons components for various frontend libraries
TypeScript
2
star
20

codemirror-lang-elixir

CodeMirror 6 mode for Elixir
JavaScript
2
star
21

svg-rewriter

add attributes to a svg file from a url
TypeScript
2
star
22

trpc-snapshot

TypeScript
1
star
23

rxjs-rollup-bundle

HTML
1
star
24

type-benchmark

A TypeScript type benchmark testing the performance of type alias vs. interfaces.
TypeScript
1
star