diff --git a/README.md b/README.md
index a5a7c54c2..b228a6ebc 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,7 @@
- [Dates](#dates)
- [Zod enums](#zod-enums)
- [Native enums](#native-enums)
+- [Not](#not)
- [Optionals](#optionals)
- [Nullables](#nullables)
- [Objects](#objects)
@@ -1133,6 +1134,27 @@ You can access the underlying object with the `.enum` property:
FruitEnum.enum.Apple; // "apple"
```
+## Not
+
+You can use `z.not()` to create a schema that rejects a specific type. This wraps the schema in a `ZodNot` instance and returns the result.
+
+```ts
+const schema = z.not(z.string());
+
+schema.parse(1234); // => passes, as 1234 is not a string
+schema.parse("hello"); // => throws an error, as "hello" is a string
+```
+
+For convenience, you can also call the `.not()` method on an existing schema.
+
+```ts
+const schema = z.string().email().not();
+
+schema.parse(1234); // => passes, as 1234 is not a string or a valid email
+schema.parse("hello"); // => passes, as "hello" is not a valid email
+schema.parse("user@example.com"); // => throws an error, as "user@example.com" is a valid email
+```
+
## Optionals
You can make any schema optional with `z.optional()`. This wraps the schema in a `ZodOptional` instance and returns the result.
diff --git a/README_ZH.md b/README_ZH.md
index 0c9ab46a0..741234f8d 100644
--- a/README_ZH.md
+++ b/README_ZH.md
@@ -65,6 +65,7 @@
- [.min/.max/.length](#minmaxlength)
- [Unions](#unions)
- [Discriminated unions](#discriminated-unions)
+- [Not](#not)
- [Optionals](#optionals)
- [Nullables](#nullables)
- [Enums](#enums)
@@ -941,6 +942,27 @@ const B = z.discriminatedUnion("status", [
const AB = z.discriminatedUnion("status", [...A.options, ...B.options]);
```
+## Not
+
+你可以用 `z.not()` 来创建一个拒绝特定类型的模式。这个方法会将模式包装在 `ZodNot` 实例中并返回结果。
+
+```ts
+const schema = z.not(z.string());
+
+schema.parse(1234); // => 通过,因为 1234 不是字符串
+schema.parse("hello"); // => 抛出错误,因为 "hello" 是字符串
+```
+
+为了方便,你也可以在现有的模式上直接调用 `.not()` 方法。
+
+```ts
+const schema = z.string().email().not();
+
+schema.parse(1234); // => 通过,因为 1234 不是字符串或有效的邮箱地址
+schema.parse("hello"); // => 通过,因为 "hello" 不是有效的邮箱地址
+schema.parse("user@example.com"); // => 抛出错误,因为 "user@example.com" 是一个有效的邮箱地址
+```
+
## Optionals
你可以用`z.optional()`使任何模式成为可选:
diff --git a/deno/lib/README.md b/deno/lib/README.md
index 84ca28ad4..b228a6ebc 100644
--- a/deno/lib/README.md
+++ b/deno/lib/README.md
@@ -83,6 +83,7 @@
- [Dates](#dates)
- [Zod enums](#zod-enums)
- [Native enums](#native-enums)
+- [Not](#not)
- [Optionals](#optionals)
- [Nullables](#nullables)
- [Objects](#objects)
@@ -252,7 +253,7 @@ Sponsorship at any level is appreciated and encouraged. If you built a paid prod
-
+
@@ -261,7 +262,7 @@ Sponsorship at any level is appreciated and encouraged. If you built a paid prod
SDKs & Terraform providers for your API
- speakeasyapi.dev
+ speakeasy.com
@@ -490,6 +491,7 @@ There are a growing number of tools that are built atop or support Zod natively!
- [`zod-prisma`](https://github.com/CarterGrimmeisen/zod-prisma): Generate Zod schemas from your Prisma schema.
- [`Supervillain`](https://github.com/Southclaws/supervillain): Generate Zod schemas from your Go structs.
- [`prisma-zod-generator`](https://github.com/omar-dulaimi/prisma-zod-generator): Emit Zod schemas from your Prisma schema.
+- [`drizzle-zod`](https://orm.drizzle.team/docs/zod): Emit Zod schemas from your Drizzle schema.
- [`prisma-trpc-generator`](https://github.com/omar-dulaimi/prisma-trpc-generator): Emit fully implemented tRPC routers and their validation schemas using Zod.
- [`zod-prisma-types`](https://github.com/chrishoermann/zod-prisma-types) Create Zod types from your Prisma models.
- [`quicktype`](https://app.quicktype.io/): Convert JSON objects and JSON schemas into Zod schemas.
@@ -1132,6 +1134,27 @@ You can access the underlying object with the `.enum` property:
FruitEnum.enum.Apple; // "apple"
```
+## Not
+
+You can use `z.not()` to create a schema that rejects a specific type. This wraps the schema in a `ZodNot` instance and returns the result.
+
+```ts
+const schema = z.not(z.string());
+
+schema.parse(1234); // => passes, as 1234 is not a string
+schema.parse("hello"); // => throws an error, as "hello" is a string
+```
+
+For convenience, you can also call the `.not()` method on an existing schema.
+
+```ts
+const schema = z.string().email().not();
+
+schema.parse(1234); // => passes, as 1234 is not a string or a valid email
+schema.parse("hello"); // => passes, as "hello" is not a valid email
+schema.parse("user@example.com"); // => throws an error, as "user@example.com" is a valid email
+```
+
## Optionals
You can make any schema optional with `z.optional()`. This wraps the schema in a `ZodOptional` instance and returns the result.
diff --git a/deno/lib/__tests__/not.test.ts b/deno/lib/__tests__/not.test.ts
new file mode 100644
index 000000000..28c2401e0
--- /dev/null
+++ b/deno/lib/__tests__/not.test.ts
@@ -0,0 +1,126 @@
+// @ts-ignore TS6133
+import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts";
+const test = Deno.test;
+
+import * as z from "../index.ts";
+
+/** Note
+ * Since z.never() allows no values, z.not(z.never()) should allow all values, including undefined. Hence, there are no failing test cases for notNever.
+ * There are no passing validation cases for notAny, as it should reject everything.
+*/
+
+// Set 1
+const str = z.string().not();
+const email = z.string().email().not();
+const num = z.number().not();
+const arr = z.array(z.string()).not();
+const rec = z.record(z.string()).not();
+const obj = z.object({ username: z.string() }).not();
+
+// Set 2
+const bigInt = z.bigint().not();
+const bool = z.boolean().not();
+const date = z.date().not();
+const sym = z.symbol().not();
+const undef = z.undefined().not();
+const nul = z.null().not();
+const voidSchema = z.void().not();
+const anySchema = z.any().not();
+const unknownSchema = z.unknown().not();
+const neverSchema = z.never().not();
+
+// Set 3
+const notString = z.not(z.string());
+const notNumber = z.not(z.number());
+const notStringArray = z.not(z.array(z.string()));
+const notRecord = z.not(z.record(z.string()));
+const notObject = z.not(z.object({ username: z.string() }));
+
+// Set 4
+const notBigInt = z.not(z.bigint());
+const notBoolean = z.not(z.boolean());
+const notDate = z.not(z.date());
+const notSymbol = z.not(z.symbol());
+const notUndefined = z.not(z.undefined());
+const notNull = z.not(z.null());
+const notVoid = z.not(z.void());
+const notAny = z.not(z.any());
+const notUnknown = z.not(z.unknown());
+const notNever = z.not(z.never());
+
+test("passing validations", () => {
+ // Set 1
+ str.parse(1234);
+ email.parse(1234);
+ email.parse("abcd");
+ num.parse("1234");
+ arr.parse([1234]);
+ rec.parse({ key: 1234 });
+ obj.parse({ username: 1234 });
+
+ // Set 2
+ bigInt.parse(10);
+ bool.parse(1234);
+ date.parse("not a date");
+ sym.parse("1234");
+ undef.parse(null);
+ nul.parse(undefined);
+ voidSchema.parse(null);
+ neverSchema.parse(undefined); // `z.never()` allows no values, so `notNever` can accept any value.
+
+ // Set 3
+ notString.parse(1234);
+ notNumber.parse("1234");
+ notStringArray.parse([1234]);
+ notRecord.parse({ key: 1234 });
+ notObject.parse({ username: 1234 });
+
+ // Set 4
+ notBigInt.parse(10);
+ notBoolean.parse(1234);
+ notDate.parse("not a date");
+ notSymbol.parse("1234");
+ notUndefined.parse(null);
+ notNull.parse(undefined);
+ notVoid.parse(null);
+ notNever.parse(undefined); // `z.never()` allows no values, so `notNever` can accept any value.
+});
+
+test("failing validations", () => {
+ // Set 1
+ expect(() => str.parse("1234")).toThrow();
+ expect(() => email.parse("sample@gmail.com")).toThrow();
+ expect(() => num.parse(1234)).toThrow();
+ expect(() => arr.parse(["1234"])).toThrow();
+ expect(() => rec.parse({ key: "value" })).toThrow();
+ expect(() => obj.parse({ username: "1234" })).toThrow();
+
+ // Set 2
+ expect(() => bigInt.parse(BigInt(10))).toThrow();
+ expect(() => bool.parse(true)).toThrow();
+ expect(() => date.parse(new Date())).toThrow();
+ expect(() => sym.parse(Symbol("symbol"))).toThrow();
+ expect(() => undef.parse(undefined)).toThrow();
+ expect(() => nul.parse(null)).toThrow();
+ expect(() => voidSchema.parse(undefined)).toThrow();
+ expect(() => anySchema.parse(undefined)).toThrow(); // `z.any()` allows any value, so `notAny` should fail for any input.
+ expect(() => unknownSchema.parse(undefined)).toThrow(); // Same as `z.any()`
+
+ // Set 3
+ expect(() => notString.parse("1234")).toThrow();
+ expect(() => notNumber.parse(1234)).toThrow();
+ expect(() => notStringArray.parse(["1234"])).toThrow();
+ expect(() => notRecord.parse({ key: "value" })).toThrow();
+ expect(() => notObject.parse({ username: "1234" })).toThrow();
+
+ // Set 4
+ expect(() => notBigInt.parse(BigInt(10))).toThrow();
+ expect(() => notBoolean.parse(true)).toThrow();
+ expect(() => notDate.parse(new Date())).toThrow();
+ expect(() => notSymbol.parse(Symbol("symbol"))).toThrow();
+ expect(() => notUndefined.parse(undefined)).toThrow();
+ expect(() => notNull.parse(null)).toThrow();
+ expect(() => notVoid.parse(undefined)).toThrow();
+ expect(() => notAny.parse(undefined)).toThrow(); // `z.any()` allows any value, so `notAny` should fail for any input.
+ expect(() => notUnknown.parse(undefined)).toThrow(); // Same as `z.any()`
+});
\ No newline at end of file
diff --git a/deno/lib/types.ts b/deno/lib/types.ts
index d634e524f..741b59f6f 100644
--- a/deno/lib/types.ts
+++ b/deno/lib/types.ts
@@ -410,6 +410,7 @@ export abstract class ZodType<
this.nullable = this.nullable.bind(this);
this.nullish = this.nullish.bind(this);
this.array = this.array.bind(this);
+ this.not = this.not.bind(this);
this.promise = this.promise.bind(this);
this.or = this.or.bind(this);
this.and = this.and.bind(this);
@@ -436,6 +437,9 @@ export abstract class ZodType<
array(): ZodArray {
return ZodArray.create(this, this._def);
}
+ not(): ZodNot {
+ return ZodNot.create(this, this._def);
+ }
promise(): ZodPromise {
return ZodPromise.create(this, this._def);
}
@@ -2295,6 +2299,62 @@ export class ZodArray<
};
}
+export interface ZodNotDef
+ extends ZodTypeDef {
+ item: T;
+ typeName: ZodFirstPartyTypeKind.ZodNot;
+}
+
+export class ZodNot extends ZodType<
+ T["_output"] | null,
+ ZodNotDef,
+ T["_input"] | null
+> {
+ _parse(input: ParseInput): ParseReturnType {
+ const { ctx } = this._processInputParams(input);
+
+ const schema = this._def.item;
+
+ if (!schema) {
+ addIssueToContext(ctx, {
+ code: ZodIssueCode.invalid_arguments,
+ argumentsError: ctx.data,
+ });
+
+ return INVALID;
+ }
+
+ const result = schema._parse(
+ new ParseInputLazyPath(ctx, ctx.data, ctx.path, 0)
+ );
+
+ if (!!result && String((result as any).status) == "valid") {
+ addIssueToContext(ctx, {
+ code: ZodIssueCode.custom,
+ message: "Invalid input",
+ });
+
+ return INVALID;
+ }
+
+ return OK(input.data);
+ }
+
+ static create = (
+ schemas: T,
+ params?: RawCreateParams
+ ): ZodNot => {
+ if (!schemas) {
+ throw new Error("You must pass a schema to z.not( ... )");
+ }
+ return new ZodNot({
+ item: schemas,
+ typeName: ZodFirstPartyTypeKind.ZodNot,
+ ...processCreateParams(params),
+ });
+ };
+}
+
export type ZodNonEmptyArray = ZodArray;
/////////////////////////////////////////
@@ -5125,6 +5185,7 @@ export enum ZodFirstPartyTypeKind {
ZodUndefined = "ZodUndefined",
ZodNull = "ZodNull",
ZodAny = "ZodAny",
+ ZodNot = "ZodNot",
ZodUnknown = "ZodUnknown",
ZodNever = "ZodNever",
ZodVoid = "ZodVoid",
@@ -5216,6 +5277,7 @@ const unknownType = ZodUnknown.create;
const neverType = ZodNever.create;
const voidType = ZodVoid.create;
const arrayType = ZodArray.create;
+const notType = ZodNot.create;
const objectType = ZodObject.create;
const strictObjectType = ZodObject.strictCreate;
const unionType = ZodUnion.create;
@@ -5274,6 +5336,7 @@ export {
nanType as nan,
nativeEnumType as nativeEnum,
neverType as never,
+ notType as not,
nullType as null,
nullableType as nullable,
numberType as number,
diff --git a/src/__tests__/not.test.ts b/src/__tests__/not.test.ts
new file mode 100644
index 000000000..467df496d
--- /dev/null
+++ b/src/__tests__/not.test.ts
@@ -0,0 +1,125 @@
+// @ts-ignore TS6133
+import { expect, test } from "@jest/globals";
+
+import * as z from "../index";
+
+/** Note
+ * Since z.never() allows no values, z.not(z.never()) should allow all values, including undefined. Hence, there are no failing test cases for notNever.
+ * There are no passing validation cases for notAny, as it should reject everything.
+*/
+
+// Set 1
+const str = z.string().not();
+const email = z.string().email().not();
+const num = z.number().not();
+const arr = z.array(z.string()).not();
+const rec = z.record(z.string()).not();
+const obj = z.object({ username: z.string() }).not();
+
+// Set 2
+const bigInt = z.bigint().not();
+const bool = z.boolean().not();
+const date = z.date().not();
+const sym = z.symbol().not();
+const undef = z.undefined().not();
+const nul = z.null().not();
+const voidSchema = z.void().not();
+const anySchema = z.any().not();
+const unknownSchema = z.unknown().not();
+const neverSchema = z.never().not();
+
+// Set 3
+const notString = z.not(z.string());
+const notNumber = z.not(z.number());
+const notStringArray = z.not(z.array(z.string()));
+const notRecord = z.not(z.record(z.string()));
+const notObject = z.not(z.object({ username: z.string() }));
+
+// Set 4
+const notBigInt = z.not(z.bigint());
+const notBoolean = z.not(z.boolean());
+const notDate = z.not(z.date());
+const notSymbol = z.not(z.symbol());
+const notUndefined = z.not(z.undefined());
+const notNull = z.not(z.null());
+const notVoid = z.not(z.void());
+const notAny = z.not(z.any());
+const notUnknown = z.not(z.unknown());
+const notNever = z.not(z.never());
+
+test("passing validations", () => {
+ // Set 1
+ str.parse(1234);
+ email.parse(1234);
+ email.parse("abcd");
+ num.parse("1234");
+ arr.parse([1234]);
+ rec.parse({ key: 1234 });
+ obj.parse({ username: 1234 });
+
+ // Set 2
+ bigInt.parse(10);
+ bool.parse(1234);
+ date.parse("not a date");
+ sym.parse("1234");
+ undef.parse(null);
+ nul.parse(undefined);
+ voidSchema.parse(null);
+ neverSchema.parse(undefined); // `z.never()` allows no values, so `notNever` can accept any value.
+
+ // Set 3
+ notString.parse(1234);
+ notNumber.parse("1234");
+ notStringArray.parse([1234]);
+ notRecord.parse({ key: 1234 });
+ notObject.parse({ username: 1234 });
+
+ // Set 4
+ notBigInt.parse(10);
+ notBoolean.parse(1234);
+ notDate.parse("not a date");
+ notSymbol.parse("1234");
+ notUndefined.parse(null);
+ notNull.parse(undefined);
+ notVoid.parse(null);
+ notNever.parse(undefined); // `z.never()` allows no values, so `notNever` can accept any value.
+});
+
+test("failing validations", () => {
+ // Set 1
+ expect(() => str.parse("1234")).toThrow();
+ expect(() => email.parse("sample@gmail.com")).toThrow();
+ expect(() => num.parse(1234)).toThrow();
+ expect(() => arr.parse(["1234"])).toThrow();
+ expect(() => rec.parse({ key: "value" })).toThrow();
+ expect(() => obj.parse({ username: "1234" })).toThrow();
+
+ // Set 2
+ expect(() => bigInt.parse(BigInt(10))).toThrow();
+ expect(() => bool.parse(true)).toThrow();
+ expect(() => date.parse(new Date())).toThrow();
+ expect(() => sym.parse(Symbol("symbol"))).toThrow();
+ expect(() => undef.parse(undefined)).toThrow();
+ expect(() => nul.parse(null)).toThrow();
+ expect(() => voidSchema.parse(undefined)).toThrow();
+ expect(() => anySchema.parse(undefined)).toThrow(); // `z.any()` allows any value, so `notAny` should fail for any input.
+ expect(() => unknownSchema.parse(undefined)).toThrow(); // Same as `z.any()`
+
+ // Set 3
+ expect(() => notString.parse("1234")).toThrow();
+ expect(() => notNumber.parse(1234)).toThrow();
+ expect(() => notStringArray.parse(["1234"])).toThrow();
+ expect(() => notRecord.parse({ key: "value" })).toThrow();
+ expect(() => notObject.parse({ username: "1234" })).toThrow();
+
+ // Set 4
+ expect(() => notBigInt.parse(BigInt(10))).toThrow();
+ expect(() => notBoolean.parse(true)).toThrow();
+ expect(() => notDate.parse(new Date())).toThrow();
+ expect(() => notSymbol.parse(Symbol("symbol"))).toThrow();
+ expect(() => notUndefined.parse(undefined)).toThrow();
+ expect(() => notNull.parse(null)).toThrow();
+ expect(() => notVoid.parse(undefined)).toThrow();
+ expect(() => notAny.parse(undefined)).toThrow(); // `z.any()` allows any value, so `notAny` should fail for any input.
+ expect(() => notUnknown.parse(undefined)).toThrow(); // Same as `z.any()`
+});
\ No newline at end of file
diff --git a/src/types.ts b/src/types.ts
index 0767073c5..207b80e5e 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -410,6 +410,7 @@ export abstract class ZodType<
this.nullable = this.nullable.bind(this);
this.nullish = this.nullish.bind(this);
this.array = this.array.bind(this);
+ this.not = this.not.bind(this);
this.promise = this.promise.bind(this);
this.or = this.or.bind(this);
this.and = this.and.bind(this);
@@ -436,6 +437,9 @@ export abstract class ZodType<
array(): ZodArray {
return ZodArray.create(this, this._def);
}
+ not(): ZodNot {
+ return ZodNot.create(this, this._def);
+ }
promise(): ZodPromise {
return ZodPromise.create(this, this._def);
}
@@ -2295,6 +2299,62 @@ export class ZodArray<
};
}
+export interface ZodNotDef
+ extends ZodTypeDef {
+ item: T;
+ typeName: ZodFirstPartyTypeKind.ZodNot;
+}
+
+export class ZodNot extends ZodType<
+ T["_output"] | null,
+ ZodNotDef,
+ T["_input"] | null
+> {
+ _parse(input: ParseInput): ParseReturnType {
+ const { ctx } = this._processInputParams(input);
+
+ const schema = this._def.item;
+
+ if (!schema) {
+ addIssueToContext(ctx, {
+ code: ZodIssueCode.invalid_arguments,
+ argumentsError: ctx.data,
+ });
+
+ return INVALID;
+ }
+
+ const result = schema._parse(
+ new ParseInputLazyPath(ctx, ctx.data, ctx.path, 0)
+ );
+
+ if (!!result && String((result as any).status) == "valid") {
+ addIssueToContext(ctx, {
+ code: ZodIssueCode.custom,
+ message: "Invalid input",
+ });
+
+ return INVALID;
+ }
+
+ return OK(input.data);
+ }
+
+ static create = (
+ schemas: T,
+ params?: RawCreateParams
+ ): ZodNot => {
+ if (!schemas) {
+ throw new Error("You must pass a schema to z.not( ... )");
+ }
+ return new ZodNot({
+ item: schemas,
+ typeName: ZodFirstPartyTypeKind.ZodNot,
+ ...processCreateParams(params),
+ });
+ };
+}
+
export type ZodNonEmptyArray = ZodArray;
/////////////////////////////////////////
@@ -5125,6 +5185,7 @@ export enum ZodFirstPartyTypeKind {
ZodUndefined = "ZodUndefined",
ZodNull = "ZodNull",
ZodAny = "ZodAny",
+ ZodNot = "ZodNot",
ZodUnknown = "ZodUnknown",
ZodNever = "ZodNever",
ZodVoid = "ZodVoid",
@@ -5216,6 +5277,7 @@ const unknownType = ZodUnknown.create;
const neverType = ZodNever.create;
const voidType = ZodVoid.create;
const arrayType = ZodArray.create;
+const notType = ZodNot.create;
const objectType = ZodObject.create;
const strictObjectType = ZodObject.strictCreate;
const unionType = ZodUnion.create;
@@ -5274,6 +5336,7 @@ export {
nanType as nan,
nativeEnumType as nativeEnum,
neverType as never,
+ notType as not,
nullType as null,
nullableType as nullable,
numberType as number,