colinhacks / zod Goto Github PK
View Code? Open in Web Editor NEWTypeScript-first schema validation with static type inference
Home Page: https://zod.dev
License: MIT License
TypeScript-first schema validation with static type inference
Home Page: https://zod.dev
License: MIT License
Just like function implementation, it would be interesting to create an object implement(readonly: bool)
method, that would create an anonymous class having the following functionality:
constructor(t?: Type)
that validates the objectassign(other: Partial<Type>)
or with(other: Partial<Type>)
, that respectively assign or copies the object and assign values, then runs validationThis would help creating ES6 classes inheriting from this base class, with validation built-in:
class MyUser extends userSchema.implement() {
set password(pw: string) {
this.passwordDigest = hash(pw);
}
}
There are a few unknowns for this: specifically, in case of nested objects that have an implementation, it would be great to return an instance of that implementation rather than a bare object. Some mapping between ZodType
and implementation could theoretically be passed to Implement.
Is it possible right now? related to #3
example:
enum Foo {
BAR = 'BAR',
BAZ = 'BAZ'
}
let e = z.enum(Foo);
e.parse("LOL"); // throws
Hi, Great wrok with zod. maybe i'm using it wrong but how would i parse a date in Api params i would get a string of Date and Zod expects a Date object. Should i use the zod.string() and parse the date in the backend?
Hey @vriad, this package looks awesome. I am wondering (and I am sure other's who are reading the README) what the difference is between this package and runtypes.
Would be great to add this info to the README as well!
When using the library to parse values, I'm seeing a lot of logs to the console of this form:
seen string before: undefined
They seem to be coming from this line in parser.ts
: https://github.com/vriad/zod/blob/f154275fafd1cd105dcfe4bc63f37313057b3171/src/parser.ts#L24
Is this intended behaviour, or was this shipped by mistake?
This lib looks like a really nice approach. I don't see support for Date
types, though. Do you have a plan to support them?
On https://vriad.com/blog/zod/ under "Custom Union Types," F.parse(null) should return "null" and not "42." It's correct on the Github README, just not on the site.
When Zod throws a validation error, specify where in the schema that error occurred. Can be very helpful for large schemas.
Split off from #14.
Hi, I love this library, it's perfect for the project I'm currently working on. I come from Joi, and it has a .default('value')
for specifying a default value if not set. Does this library have something similar? is that a possibility?
Thanks
I'm developing a aws lambda based application, that means I cannot direcly use typescript code when deploying, when I tried to deploy a lambda using zod, my webpack process failed with the following error:
Error: TypeScript emitted no output for /Users/osp/dev/strestsaas/server/node_modules/zod/index.ts. By default, ts-loader will not compile .ts files in node_modules.
You should not need to recompile .ts files there, but if you really want to, use the allowTsInNodeModules option.
I think the published version of the package should not directly link to .ts files in order for transpiled projects to work, right?
Are there plans to support other primitives such as Promise and BigInt?
When trying to parse a union type you get a very generic error that doesn't tell you what actually went wrong.
import * as z from 'zod';
enum ContactType {
Address = 'ADDRESS',
Phone = 'PHONE',
}
const addressSchema = z.object({
kind: z.literal(ContactType.Address),
address: z.object({
streetAddress: z.string(),
city: z.string(),
stateOrProvince: z.string(),
zipCode: z.string(),
}),
});
const phoneSchema = z.object({
kind: z.literal(ContactType.Phone),
phone: z.string(),
});
const contactSchema = z.union([ addressSchema, phoneSchema ]);
const data = {
kind: ContactType.Address,
address: {
streetAddress: '123 Fake Street',
city: 'Springfield',
stateOrProvince: 'OH',
// Missing zipCode
},
};
contactSchema.parse(data);
This will give you:
Error parsing union.
Received: {
"kind": "ADDRESS",
"address": {
"streetAddress": "123 Fake Street",
"city": "Springfield",
"stateOrProvince": "OH"
}
}
Expected: object OR object
If you change the last line to addressSchema.parse(data);
you get something useful:
`address.zipCode`: Non-string type: undefined
It would be nice if this worked more like TypeScript itself does when type checking unions and told you why each possible type in the union failed to work. Perhaps something like:
object: Unexpected key(s) in object: 'address'
object: `address.zipCode`: Non-string type: undefined
Even better would be if it could give something more useful than object
for the names of these but I'm not sure how you'd accomplish that without adding some means of providing metadata for types.
Just like how https://github.com/gcanti/babel-plugin-tcomb works.
This feature causes yup to throw an error on validation if there's a property on the object that doesn't match the schema. Similar to yup.object().noUnknown()
Split off from #14.
I've been converting lots of TS types to zod. It's going pretty smoothly. Again, thanks for the excellent work on this lib! But some of the types are fairly complex with nested intersections and unions of object types, and I find myself really wishing I could use the ZodObject methods on these types, especially pick
, omit
, and partial
.
So for a zod definition like this:
const schema = z.intersection(
z.object({key1: z.string()}),
z.union([
z.object({key2: z.string()}),
z.object({key3: z.string()}),
])
);
I'd love to be able to do:
schema.pick({key1: true, key3: true});
schema.omit({key2: true});
schema.partial();
And same for other structures: unions of objects, unions of intersections, etc.
It seems like it could be doable to call them recursively through all levels of nesting with the appropriate subset of keys as long as every intersection/union resolves to an object? Maybe type inference is an issue?
I'm getting around this by breaking up complex types into many constituent ZodObject schemas, and then re-composing them using the object methods as needed, but it would be a lot easier to just call the methods directly on the full schema.
The following schema
export const CellResultsAPIValidator = z.union([
z.object({
kind: z.literal("error"),
error: z.object({
message: z.string(),
type: z.string(),
}),
}),
z.object({
kind: z.literal("results"),
results: z
.array(
z.union([
z.object({
kind: z.literal("data"),
data: z.tuple([z.string(), z.string(), z.string()]),
}),
z.object({
kind: z.literal("error"),
error: z.tuple([z.string(), z.string(), z.object({})]),
}),
z.object({
kind: z.literal("skipped"),
}),
])
)
.nonempty(),
}),
]);
no longer validates the following
{ Error: Type mismatch in union.
Received: {
"kind": "results",
"results": [
{
"kind": "error",
"error": [
"InvalidReferenceError",
"object type or alias 'User' does not exist",
{
"65521": "7",
"65522": "11",
"65523": "1",
"65524": "8"
}
]
}
]
}
Expected: object OR object
at new ZodError (/Users/yury/dev/edge/tutorial/node_modules/zod/lib/src/ZodError.js:20:28)
at Function.ZodError.create (/Users/yury/dev/edge/tutorial/node_modules/zod/lib/src/ZodError.js:66:21)
at Function.ZodError.fromString (/Users/yury/dev/edge/tutorial/node_modules/zod/lib/src/ZodError.js:71:25)
at ZodUnion.parse (/Users/yury/dev/edge/tutorial/node_modules/zod/lib/src/parser.js:159:39)
after upgrading from 1.0.9 to 1.3.0
I've looked through the docs, but I'm unclear on how to achieve a dictionary type that has unknown string keys, but known values, basically the type of Record<string, KnownType | undefined>
. The nostrict()
modifier is close, but I don't see how to specify the value type when using that
Spun off from #16.
See the discussion there about masking.
Hi!
I am wondering what are the differences between Zod and typescript type guards. What are the pros/cons/limits/purposes for each.
Hey man! Great job with the library! Coming from io-ts I really like how similar the syntax is. I'm looking for a feature that I have yet to find in any schema validation libraries. The ability to validate both complete and partial input against a single defined schema.
The use case is this. I have an API that allows creation and update of a resource. Both operations need to validate the input. In the creation case, the input needs to strictly adhere to the schema. In the update case, any partial input that matches a subset of the schema should be accepted.
I can accomplish this with any schema validation library by creating 2 schemas but my schemas are quite large and this would be painful. It would be great to be able to either:
Thanks for contributing this library to the community!
Often when calling an HTTP endpoint, we're only interested in small bits of the data, but not the whole response. I, therefore, tried to model a "zod" (don't know what you call it :) ) based on that shape. Of course, I got an error because of unexpected types. The docs pointed me to "nonstrict" mode, which works great. I have a guarantee that the properties I need are there. The only issue is that it's not typesafe...the "nonstrict" mode carries over to the other end, and I can ask for any property I want, even if it doesn't exist.
Is there a hybrid mode? Or perhaps a setting in parse that doesn't error when coming across unexpected properties.
Zod version: 1.5.0
TS version: 3.8.3
The type inference does not seem to be working properly, making all values of objects to be optional by default.
Let me take this code derived from the readme:
import * as z from "zod";
const dogSchema = z.object({
name: z.string(),
neutered: z.boolean(),
});
type T = z.TypeOf<typeof dogSchema>; // -> { name?: string, neutered?: boolean }
const dog: T = {}; // -> Compiles without error
The expected behavior would be a compilation error deriving from name
and neutered
to be required.
import { Bananas } from '@fruit/banana'
import * as z from 'zod'
const schema = z.object({
apple: z.string(),
banana: z.doSomethingWith3pType() // z.object({}) ??
})
const myType = z.infer<typeof schema> // { apple: string, banana: ???? }
what the best way to get my 3p schema in? assuming because types have been compiled away, getting my 3p schema may not be possible, but, is there a mechanism to put my type into the schema, and get it reflected back out?
Love this library! I'm just beginning to convert a large TS project with many types, and I'm wondering how feasible it would be to automatically run through a file and convert any TS types to a zod schema and the accompanying inferred type. Or perhaps using the language server somehow would be easier? Either way, it would remove a ton of tedium from adopting zod for an existing project.
I guess I'll just barrel through and convert them one by one, which will probably take me a few hours at least, but figured I'd suggest this for those who might end up in a similar spot.
First, Thank you very much for your awesome work ๐
I figured out when I'm using z.infer
on an object schema, the inferred type has all the object's attributes optional. According to the doc, all the attributes should be required.
Example from the doc:
const dogSchema = z.object({
name: z.string(),
neutered: z.boolean(),
});
type Dog = z.infer<typeof dogSchema>;
/*
equivalent to:
type Dog = {
name:string;
neutered: boolean;
}
*/
But this is what I get:
type Dog = {
name?: string;
neutered?: boolean;
}
I'm using zod 1.5.0
and I've also tried with 1.3.0
but keep getting the Dog type with optional attributes.
Thanks for your help!
Hey @vriad, the package looks great.
I added it to the benchmark repo.
https://github.com/moltar/typescript-runtime-type-benchmarks
My webpack throwing a lot of warnings about sourcemaps.
WARNING in ./node_modules/zod/lib/types/string.js
Module Warning (from ./node_modules/source-map-loader/index.js):
(Emitted value instead of an instance of Error) Cannot find source file '../../src/types/string.ts': Error: Can't resolve '../../src/types/string.ts' in '/Users/okhomenko/brightback/brightback-server/src/main/webpack/node_modules/zod/lib/types'
WARNING in ./node_modules/zod/lib/types/object.js
Module Warning (from ./node_modules/source-map-loader/index.js):
(Emitted value instead of an instance of Error) Cannot find source file '../../src/types/object.ts': Error: Can't resolve '../../src/types/object.ts' in '/Users/okhomenko/brightback/brightback-server/src/main/webpack/node_modules/zod/lib/types'
It's trying to merge sourcemaps from zod and can't find source typescript files.
@vriad Could you please include src as part of the published package?
I tried to use Zod and cannot get even the most basic example from the documentation to compile.
Here is the code:
import zod from 'zod';
zod.object({
name: zod.string(),
neutered: zod.boolean(),
});
Using typescript 3.8.3 with tsConfig:
{
"compilerOptions": {},
"include": ["src/**/*"]
}
Finally the message typescript provides:
node_modules/zod/lib/types/object.d.ts:46:43 - error TS2344: Type '{ [k in Exclude<keyof Params, "strict">]: Params[k]; } & { strict: false; }' does not satisfy the constraint 'ZodRawShape'.
Property 'strict' is incompatible with index signature.
Type 'false' is not assignable to type 'ZodAny'.
46 nonstrict: () => ZodObject<T, Flatten<{ [k in Exclude<keyof Params, "strict">]: Params[k]; } & {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
47 strict: false;
~~~~~~~~~~~~~~~~~~~~~~
48 }>>;
~~~~~
I just found myself needing a generic type for any valid JSON and put this together:
type Literal = boolean | null | number | string;
type Json = Literal | { [key: string]: Json } | Json[];
const Literal = Zod.union([Zod.boolean(), Zod.null(), Zod.number(), Zod.string()]);
const Json: Zod.ZodType<Json> = Zod.lazy(() =>
Zod.union([Literal, Zod.array(Json), Zod.record(Json)])
);
Putting this in an issue for anyone who might be on the lookout or if the library might want to integrate a higher-level utility constructs like this.
I've been thinking a lot about validation libaries and I share your frustration with Yup. Yup does not get everything right. I'm scared about to introducing io-ts is it has a high a cognitive overhead, but I really love what io-ts does.
I think I'll experiment with this libarary a bit, but in terms of io-ts features do you think zod could be improve with these features?
is()
method on codecs.assert()
) using the new assertion function types in typescript 3.7 (io-ts doesn't actually have this probably because it wants us to use Either
instead of exceptions)NonZeroNumber
or Email
It was worked in 1.0.9 , but broken in 1.2.3 .
export const stackProps = z
.object({
direction: Direction.optional(),
horizontalAlign: Alignment.optional(),
verticalAlign: Alignment.optional(),
wrap: z.boolean().optional(),
grow: z.boolean().optional(),
shrink: z.boolean().optional(),
})
type PropType = z.TypeOf<typeof stackProps>
It would be cool if the variation of the below code is allowed:
const CellText = z.object({
kind: "text",
body: z.string(),
});
const CellCode = z.object({
kind: "code",
code: z.string(),
});
const Schema = z.array(
z.object({
category: z.string(),
cells: z.array(z.union([CellText, CellCode])).nonempty(),
})
);
Hi,
I have read your article and reasoning for creating this lib. And I first, ye cool correct types, I will use it.
But then I see that
My question, where do you use it, so you can live without the above features?
Hey! I just tried zod
and I quite enjoy it so far, great job! I have a feature request/discussion.
Problem: I would like my schema to not only validate data but transform it as well. A particular use case is that I would like to specify a mapping in my object schema.
One could imagine a type z.stringAsNumber()
which would parse strings like "123"
to a number 123
. Something like this is totally possible with io-ts
.
Or even more powerful case. Where object validation could remap keys:
const envSchema = z.object({
SERVER_HOST: z.string(),
PUBLIC_S3_URL: z.string(),
PRIVATE_BACKEND_URL: z.string(),
})
This schema is used to validate environment variables. It would be cool if I could specify a key mapping to end up with renamed keys in a parsed object for example (example API)
const envSchema = z.object({
serverHost: { value: z.string(), key: 'SERVER_HOST' },
publicS3: { value: z.string(), key: 'PUBLIC_S3_URL' }
privateBackendURL: { z.string(), key: 'PRIVATE_BACKEND_URL' }
})
I feel like this is a very common situation. Of course, it can be done manually after schema validation but I believe that it would be cool to support this one way or another. WDYT @vriad? Is this something that you want to support eventually?
Because zod isn't compiled when it's distributed in npm, my TS compiler tries to compile it. However, my project has strict: true
in its config options, with no strict flags turned off, and so it ends up failing to build because zod has strictPropertyInitialization
set to false. I get a handful of errors like Property 'schema' has no initializer and is not definitely assigned in the constructor
I currently can only use this library in my app if my strict compile options are set to what zod has, which is definitely not preferable. (And that's a shame because I really like the idea of this and I too was this close to trying to write my own library after being unhappy with the status quo.)
I think it would help a lot if the project was actually pre-compiled, and the JS + .d.ts files were distributed in npm, instead of the raw TS files.
(Also, side note that is kind of related to my above suggestion: I run our app using ts-node, and I get Cannot use import statement outside a module
when ts-node tries to import zod. I'm pretty sure that this would also be fixed by releasing the compiled code instead of the raw TS.)
Feature request: branded types
This issue can be the primary discussion ground for implemented branded types.
Option 1:
Replicating io-ts's symbol trickery to create types like NonZeroNumber or Email (it might be difficult to do that one while remaining ergonomic)
I don't love all the boilerplate associated with io-ts
branded types (i.e. interface PositiveBrand { readonly Positive: unique symbol }
).
Option 2
It may be able to include string/number validators as a literal generic argument of the ZodString/ZodNumber classes, like so:
const num: z.number({ max: 5 }) // => z.ZodString
const max5: z.number({ max: 5 }) // => z.ZodString<{ max: 5 }>
That way the validations being enforced are easy to see with a glance at the type definition. This would only work for built-in validators I believe (?). This is also different, in that validations are registered at the instance level instead of the class level.
I'm getting .parse
errors that seem incorrect with a schema similar to this one:
z.intersection(
z.object({
user: z.object({
email: z.string(),
firstName: z.string(),
lastName: z.string()
}),
org: z.object({ name: z.string()})
}),
z.union([
z.object({
provider: z.literal("email"),
emailVerificationToken: z.string(),
}),
z.object({
provider: z.enum(["github", "gitlab"]),
externalAuthSessionId: z.string(),
}),
])
)
But when using .parse
or .check
on an object that matches the inferred type, I get missing key errors.
So with a seemingly valid object like this one (that type checks fine with the inferred type):
{
user: { email: "[email protected]", firstName: "Test", lastName: "User"},
org: { name: "Test Org" },
provider: "email",
emailVerificationToken: "token"
}
I get the error:
Left side of intersection: Unexpected key(s) in object: 'provider', 'emailVerificationToken'\nRight side of intersection: \n\tunion option #0: Unexpected key(s) in object: 'org', 'user'\n\tunion option #1: Unexpected key(s) in object: 'org', 'user', 'emailVerificationToken'
Is this a bug or am I misunderstanding something about how intersections and unions work in zod?
Is there any plan to support custom validation?
It could be of the form: z.string().validate(someValidationFunction)
.
That would be a great help to build advanced schemas.
This could also take the form of custom types, but for now, z.ZodType
/ z.ZodTypeDef
not being exposed as part of the API make it a hack IMO.
Coming from io-ts
there were useful t.any()
or t.unknown()
types for accepting anything but not making many assertions about what values contained. Is that something that fits into the Zod vision?
Hi!
Awesome work on this lib, I was waiting for years for someone to tackle this problem ;)
const dogsList = z.array(dogSchema);
dogsList.parse([
{ name: "Cujo", neutered: null },
{ name: "Fido", age: 4, neutered: true },
]) // passes
dogsList.parse([]) // passes
I believe this shouldn't pass because of the lack of age
in the first item right?
Typescript โ TypeScript
Sorry, pet peeve of mine. Cool library! :)
Is there a way to hook into the validation error messages to provide custom ones?
This would be useful for client-side validation when the errors are user-facing and might need to be translated.
I'm really impressed with this project and its ease of use. There are a couple of features from yup that would be really useful.
#1. yup.object().noUnknown() - This feature causes yup to throw an error on validation if there's a property on the object that doesn't match the schema
#2. ValidationError.path - When yup throws a validation error, it specifies where in the schema that error occurred which can be very helpful for large schemas
My tests seem to be failing after the upgrade. Any ideas?
moltar/typescript-runtime-type-benchmarks#155
https://github.com/moltar/typescript-runtime-type-benchmarks/pull/155/checks?check_run_id=553968076
// Before: `.number()`, `.optional()` etc. as methods
const union = z.union([z.number(), z.boolean()]);
const optionalString = z.string().optional();
// After: `.number()`, `.optional()` etc. as getters
const union = z.union([z.number, z.boolean]);
const optionalString = z.string.optional;
That file doesn't exist. Did you forget to include it?
In TS, I can define an object like so:
type Flags = Record<"flag1"|"flag2", boolean>
or:
type Flags = {[k in "flag1"|"flag2"]: boolean}
It doesn't seem there's any way to accomplish this now in zod? I have lots of TS types using this pattern with large unions that I really don't want to duplicate everywhere as z.object
keys, so this would be handy to get full parity with TS.
I'm wondering how to do this option from yum:
stripUnknown
:remove unspecified keys from objects.
I can pluck or omit, but what if I only want to pick the properties on the ZodObject?
For example:
const Animal = z.object({
name: z.string(),
}),
export type Animal = z.infer<
typeof Animal
>;
But, it so happens that an object such as { name: 'tommy', kind: 'cat' }
comes along. How do I only validate the properties on Animal and drop others like kind
?
Since I am returned the same object and .nonstrict allows me to still verify the keys I care about, I would like to be able to drop the unknown keys that are on the object.
The zod object Animal has a .toJSON which return the shape and I can get keys this way:
Animal.toJSON().shape['0']
As of version 1.2.5
, the parse
, is
and check
methods on ZodType
expect to receive an argument which is already of the schema's type. This is surprising to me; at version 1.0.15
I was passing unknown
values to these functions, since the input is of unknown type (typically from IO) and the functions are being called explicity to discover whether the value matches the type.
Since I don't want to incorrectly-type the values before they are checked, it seems the only option here is to use any
, which from my point of view is strictly worse than unknown
.
Is there a downside I'm not seeing to permitting unknown
as input to these functions?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.