Gathering detailed insights and metrics for ts-to-zod
Gathering detailed insights and metrics for ts-to-zod
Gathering detailed insights and metrics for ts-to-zod
Gathering detailed insights and metrics for ts-to-zod
zod-to-json-schema
Converts Zod schemas to Json Schemas
zod
TypeScript-first schema declaration and validation library with static type inference
zod-to-ts
generate TypeScript types from your Zod schema
zod-validation-error
Wrap zod validation errors in user-friendly readable messages
npm install ts-to-zod
Module System
Min. Node Version
Typescript Support
Node Version
NPM Version
1,237 Stars
224 Commits
69 Forks
11 Watching
11 Branches
24 Contributors
Updated on 27 Nov 2024
TypeScript (99.48%)
JavaScript (0.48%)
Shell (0.03%)
Batchfile (0.01%)
Cumulative downloads
Total Downloads
Last day
15.1%
13,484
Compared to previous day
Last week
19.1%
76,453
Compared to previous week
Last month
11.5%
289,443
Compared to previous month
Last year
186.1%
2,545,915
Compared to previous year
16
21
Generate Zod schemas (v3) from Typescript types/interfaces.
1$ yarn add --dev ts-to-zod 2$ yarn ts-to-zod src/iDontTrustThisApi.ts src/nowIcanValidateEverything.ts
That's it, go to src/nowIcanValidateEverything.ts
file, you should have all the exported interface
and type
as Zod schemas with the following name pattern: ${originalType}Schema
.
To make sure the generated zod schemas are 100% compatible with your original types, this tool is internally comparing z.infer<generatedSchema>
and your original type. If you are running on those validation, please open an issue 😀
Notes:
--skipValidation
. (At your own risk!)This tool supports some JSDoc tags (inspired by OpenAPI) to generate additional Zod schema validators.
List of supported keywords:
JSDoc keyword | JSDoc Example | Generated Zod validator |
---|---|---|
@minimum {number} [err_msg] | @minimum 42 | z.number().min(42) |
@maximum {number} [err_msg] | @maximum 42 Must be < 42 | z.number().max(42, "Must be < 42") |
@minLength {number} [err_msg] | @minLength 42 | z.string().min(42) |
@maxLength {number} [err_msg] | @maxLength 42 | z.string().max(42) |
@format {FormatType} [err_msg] | @format email | z.string().email() |
@pattern {regex} Note: Due to parsing ambiguities, @pattern does not support generating error messages. | @pattern ^hello | z.string().regex(/^hello/) |
By default, FormatType
is defined as the following type (corresponding Zod validator in comment):
1type FormatType = 2 | "date-time" // z.string().datetime() 3 | "date" // z.string().date() 4 | "time" // z.string().time() 5 | "duration" // z.string().duration() 6 | "email" // z.string().email() 7 | "ip" // z.string().ip() 8 | "ipv4" // z.string().ip() 9 | "ipv6" // z.string().ip() 10 | "url" // z.string().url() 11 | "uuid"; // z.string().uuid()
However, see the section on Custom JSDoc Format Types to learn more about defining other types of formats for string validation.
Those validators can be combined:
1// source.ts 2export interface HeroContact { 3 /** 4 * The email of the hero. 5 * 6 * @format email 7 */ 8 email: string; 9 10 /** 11 * The name of the hero. 12 * 13 * @minLength 2 14 * @maxLength 50 15 */ 16 name: string; 17 18 /** 19 * The phone number of the hero. 20 * 21 * @pattern ^([+]?d{1,2}[-s]?|)d{3}[-s]?d{3}[-s]?d{4}$ 22 */ 23 phoneNumber: string; 24 25 /** 26 * Does the hero has super power? 27 * 28 * @default true 29 */ 30 hasSuperPower?: boolean; 31 32 /** 33 * The age of the hero 34 * 35 * @minimum 0 36 * @maximum 500 37 */ 38 age: number; 39} 40 41// output.ts 42export const heroContactSchema = z.object({ 43 /** 44 * The email of the hero. 45 * 46 * @format email 47 */ 48 email: z.string().email(), 49 50 /** 51 * The name of the hero. 52 * 53 * @minLength 2 54 * @maxLength 50 55 */ 56 name: z.string().min(2).max(50), 57 58 /** 59 * The phone number of the hero. 60 * 61 * @pattern ^([+]?d{1,2}[-s]?|)d{3}[-s]?d{3}[-s]?d{4}$ 62 */ 63 phoneNumber: z.string().regex(/^([+]?d{1,2}[-s]?|)d{3}[-s]?d{3}[-s]?d{4}$/), 64 65 /** 66 * Does the hero has super power? 67 * 68 * @default true 69 */ 70 hasSuperPower: z.boolean().default(true), 71 72 /** 73 * The age of the hero 74 * 75 * @minimum 0 76 * @maximum 500 77 */ 78 age: z.number().min(0).max(500), 79});
Other JSDoc tags are available:
JSDoc keyword | JSDoc Example | Description | Generated Zod |
---|---|---|---|
@description {value} | @description Full name | Sets the description of the property | z.string().describe("Full name") |
@default {value} | @default 42 | Sets a default value for the property | z.number().default(42) |
@strict | @strict | Adds the strict() modifier to an object | z.object().strict() |
@schema | @schema .catch('foo') | If value starts with a . , appends the specified value to the generated schema. Otherwise this value will override the generated schema. | z.string().catch('foo') |
union
typesJSDoc keyword | JSDoc Example | Description | Generated Zod |
---|---|---|---|
@discriminator {propName} | @discriminator id | Generates a z.discriminatedUnion() instead of union | z.discriminatedUnion("id", ...) |
Example:
1// source.ts 2/** 3 * @discriminator type 4 **/ 5export type Person = 6 | { type: "Adult"; name: string } 7 | { type: "Child"; age: number }; 8 9// output.ts 10// Generated by ts-to-zod 11import { z } from "zod"; 12 13export const personSchema = z.discriminatedUnion("type", [ 14 z.object({ 15 type: z.literal("Adult"), 16 name: z.string(), 17 }), 18 z.object({ 19 type: z.literal("Child"), 20 age: z.number(), 21 }), 22]);
string
and number
arraysElements of string
and number
arrays can be validated using the following JSDoc tags (for details see above).
JSDoc keyword |
---|
@elementDescription {value} |
@elementMinimum {number} [err_msg] |
@elementMaximum {number} [err_msg] |
@elementMinLength {number} [err_msg] |
@elementMaxLength {number} [err_msg] |
@elementFormat {FormatType} [err_msg] |
@elementPattern {regex} |
Example:
1// source.ts 2export interface EnemyContact { 3 /** 4 * The names of the enemy. 5 * 6 * @elementMinLength 5 7 * @elementMaxLength 10 8 * @minLength 2 9 * @maxLength 50 10 */ 11 names: string[]; 12 13 /** 14 * The phone numbers of the enemy. 15 * 16 * @description Include home and work numbers 17 * @elementPattern ^([+]?d{1,2}[-s]?|)d{3}[-s]?d{3}[-s]?d{4}$ 18 */ 19 phoneNumbers: string[]; 20} 21 22// output.ts 23export const enemyContactSchema = z.object({ 24 /** 25 * The names of the enemy. 26 * 27 * @elementMinLength 5 28 * @elementMaxLength 10 29 * @minLength 2 30 * @maxLength 50 31 */ 32 name: z.array(z.string().min(5).max(10)).min(2).max(50), 33 34 /** 35 * The phone numbers of the enemy. 36 * 37 * @elementPattern ^([+]?d{1,2}[-s]?|)d{3}[-s]?d{3}[-s]?d{4}$ 38 */ 39 phoneNumbers: z 40 .array(z.string().regex(/^([+]?d{1,2}[-s]?|)d{3}[-s]?d{3}[-s]?d{4}$/)) 41 .describe("Include home and work numbers"), 42});
If you want to customize the schema name or restrict the exported schemas, you can do this by adding a ts-to-zod.config.js
at the root of your project.
Just run yarn ts-to-zod --init
and you will have a ready to use configuration file (with a bit of type safety).
You have two ways to restrict the scope of ts-to-zod:
nameFilter
will filter by interface/type namejsDocTagFilter
will filter on jsDocTagExample:
1// ts-to-zod.config.js 2/** 3 * ts-to-zod configuration. 4 * 5 * @type {import("./src/config").TsToZodConfig} 6 */ 7module.exports = [ 8 { 9 name: "example", 10 input: "example/heros.ts", 11 output: "example/heros.zod.ts", 12 jsDocTagFilter: (tags) => tags.map((tag) => tag.name).includes("toExtract"), // <= rule here 13 }, 14]; 15 16// example/heros.ts 17/** 18 * Will not be part of `example/heros.zod.ts` 19 */ 20export interface Enemy { 21 name: string; 22 powers: string[]; 23 inPrison: boolean; 24} 25 26/** 27 * Will be part of `example/heros.zod.ts` 28 * @toExtract 29 */ 30export interface Superman { 31 name: "superman" | "clark kent" | "kal-l"; 32 enemies: Record<string, Enemy>; 33 age: number; 34 underKryptonite?: boolean; 35}
Please note: if your exported interface/type have a reference to a non-exported interface/type, ts-to-zod will not be able to generate anything (missing dependencies will be reported).
ts-to-zod
already supports converting several @format
types such as email
and ip
to built-in Zod string validation functions. However, the types supported out of the box are only a subset of those recognized by the OpenAPI specification, which doesn't fit every use case. Thus, you can use the config file to define additional format types using the customJSDocFormatTypes
property like so:
1{ 2 "customJSDocFormatTypes": { 3 [formatTypeNoSpaces]: 4 | string 5 | {regex: string, errorMessage: string} 6 } 7}
Here is an example configuration:
1{ 2 "customJSDocFormatTypes": { 3 "phone-number": "^\\d{3}-\\d{3}-\\d{4}$", 4 "date": { 5 "regex": "^\\d{4}-\\d{2}-\\d{2}$", 6 "errorMessage": "Must be in YYYY-MM-DD format." 7 } 8 } 9}
As a result, ts-to-zod
will perform the following transformation:
TypeScript | Zod |
---|---|
|
|
Since we are generating Zod schemas, we are limited by what Zod actually supports:
Record<number, …>
To resume, you can use all the primitive types and some the following typescript helpers:
Record<string, …>
Pick<>
Omit<>
Partial<>
Required<>
Array<>
Promise<>
This utility is designed to work with one file at a time (it will not split one file into several), and will handle references to types from the same file:
1// source.ts 2export type Id = string; 3export interface Hero { 4 id: Id; 5 name: string; 6} 7 8// output.ts 9export const idSchema = z.string(); 10export const heroSchema = z.object({ 11 id: idSchema, 12 name: z.string(), 13});
ts-to-zod
can reference imported types from other modules but will use an any
validation as placeholder, as we cannot know
their schema.
1// source.ts 2import { Person } from "@3rdparty/person"; 3import { Villain } from "./villain"; 4 5export interface Hero { 6 name: string; 7 realPerson: Person; 8 nemesis: Villain; 9} 10 11// output.ts 12 13const personSchema = z.any(); 14 15const villainSchema = z.any(); 16 17export const heroSchema = z.object({ 18 name: z.string(), 19 realPerson: personSchema, 20 nemesis: villainSchema; 21});
If an imported type is referenced in the ts-to-zod.config.js
config (as input), this utility will automatically replace the import with the given output from the file, resolving the relative paths between both.
1 2//ts-to-zod.config.js 3/** 4 * ts-to-zod configuration. 5 * 6 * @type {import("./src/config").TsToZodConfig} 7 */ 8module.exports = [ 9 { 10 name: "villain", 11 input: "src/villain.ts", 12 output: "src/generated/villain.zod.ts", 13 getSchemaName: (id) => `z${id}` 14 }, 15 { 16 name: "hero", 17 input: "src/example/heros.ts", 18 output: "src/example/heros.zod.ts", 19 }, 20]; 21 22// heros.ts (input) 23import { Person } from "@3rdparty/person"; 24import { Villain } from "../villain"; 25 26export interface Hero { 27 name: string; 28 realPerson: Person; 29 nemesis: Villain; 30} 31 32// heros.zod.ts (output) 33import { zVillain } from "../generated/villain.zod"; 34 35const personSchema = z.any(); 36 37export const heroSchema = z.object({ 38 name: z.string(), 39 realPerson: personSchema, 40 nemesis: zVillain; 41});
You need more than one file? Want even more power? No problem, just use the tool as a library.
High-level function:
generate
take a sourceText
and generate two file gettersPlease have a look to src/core/generate.test.ts
for more examples.
Low-level functions:
generateZodSchema
help you to generate export const ${varName} = ${zodImportValue}.object(…)
generateZodInferredType
help you to generate export type ${aliasName} = ${zodImportValue}.infer<typeof ${zodConstName}>
generateIntegrationTests
help you to generate a file comparing the original types & zod typesTo learn more about those functions or their usages, src/core/generate.ts
is a good starting point.
1$ git clone 2$ cd ts-to-zod 3$ yarn 4$ yarn build 5$ ./bin/run 6USAGE 7 $ ts-to-zod [input] [output] 8 ...
You also have plenty of unit tests to play safely:
1$ yarn test --watch
And a playground inside example
, buildable with the following command:
1$ yarn gen:example
Last note, if you are updating src/config.ts
, you need to run yarn gen:config
to have generate the schemas of the config (src/config.zod.ts
) (Yes, we are using the tool to build itself #inception)
Have fun!
No vulnerabilities found.
Reason
9 commit(s) and 18 issue activity found in the last 90 days -- score normalized to 10
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
packaging workflow detected
Details
Reason
Found 6/18 approved changesets -- score normalized to 3
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
branch protection not enabled on development/release branches
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Reason
11 existing vulnerabilities detected
Details
Score
Last Scanned on 2024-11-25
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More