From c35b397d7395546c881de05145a65d07777b360c Mon Sep 17 00:00:00 2001 From: Henrique Bruno Date: Fri, 5 Aug 2022 14:24:40 +0100 Subject: [PATCH 1/2] feat: .required(mask) --- deno/lib/README.md | 37 ++++++++++++++++++++++++++ deno/lib/__tests__/partials.test.ts | 17 +++++++++++- deno/lib/types.ts | 41 +++++++++++++++++++++++------ src/__tests__/partials.test.ts | 17 +++++++++++- src/types.ts | 41 +++++++++++++++++++++++------ 5 files changed, 135 insertions(+), 18 deletions(-) diff --git a/deno/lib/README.md b/deno/lib/README.md index 56bde2029..693374ee6 100644 --- a/deno/lib/README.md +++ b/deno/lib/README.md @@ -328,6 +328,7 @@ There are a growing number of tools that are built atop or support Zod natively! - [`zod-xlsx`](https://github.com/sidwebworks/zod-xlsx): A xlsx based resource validator using Zod schemas. - [`remix-domains`](https://github.com/SeasonedSoftware/remix-domains/): Improves end-to-end type safety in [Remix](https://remix.run/) by leveraging Zod to parse the framework's inputs such as FormData, URLSearchParams, etc. - [`@zodios/core`](https://github.com/ecyrbe/zodios): A typescript API client with runtime and compile time validation backed by axios and zod. +- [`@runtyping/zod`](https://github.com/johngeorgewright/runtyping/tree/master/packages/zod): Generate zod from static types & JSON schema. #### Form integrations @@ -924,6 +925,42 @@ const deepPartialUser = user.deepPartial(); > Important limitation: deep partials only work as expected in hierarchies of objects, arrays, and tuples. + +### `.required` + +Contrary to the `.partial` method, the `.required` method makes all properties required. + +Starting from this object: + +```ts +const user = z.object({ + email: z.string() + username: z.string(), +}).partial(); +// { email?: string | undefined; username?: string | undefined } +``` + +We can create a required version: + +```ts +const requiredUser = user.required(); +// { email: string; username: string } +``` + +You can also specify which properties to make required: + +```ts +const requiredEmail = user.required({ + email: true, +}); +/* +{ + email: string; + username?: string | undefined; +} +*/ +``` + ### `.passthrough` By default Zod object schemas strip out unrecognized keys during parsing. diff --git a/deno/lib/__tests__/partials.test.ts b/deno/lib/__tests__/partials.test.ts index 8646d82e3..df8ba5951 100644 --- a/deno/lib/__tests__/partials.test.ts +++ b/deno/lib/__tests__/partials.test.ts @@ -147,7 +147,22 @@ test("required", () => { expect(requiredObject.shape.field).toBeInstanceOf(z.ZodDefault); }); -test("with mask", async () => { +test("required with mask", () => { + const object = z.object({ + name: z.string(), + age: z.number().optional(), + field: z.string().optional().default("asdf"), + country: z.string().optional(), + }); + + const requiredObject = object.required({ age: true }); + expect(requiredObject.shape.name).toBeInstanceOf(z.ZodString); + expect(requiredObject.shape.age).toBeInstanceOf(z.ZodNumber); + expect(requiredObject.shape.field).toBeInstanceOf(z.ZodDefault); + expect(requiredObject.shape.country).toBeInstanceOf(z.ZodOptional); +}); + +test("partial with mask", async () => { const object = z.object({ name: z.string(), age: z.number().optional(), diff --git a/deno/lib/types.ts b/deno/lib/types.ts index 4fb3c0c3a..8495dad2d 100644 --- a/deno/lib/types.ts +++ b/deno/lib/types.ts @@ -1915,16 +1915,41 @@ export class ZodObject< { [k in keyof T]: deoptional }, UnknownKeys, Catchall - > { + >; + required( + mask: Mask + ): ZodObject< + objectUtil.noNever<{ + [k in keyof T]: k extends keyof Mask ? deoptional : T[k]; + }>, + UnknownKeys, + Catchall + >; + required(mask?: any) { const newShape: any = {}; - for (const key in this.shape) { - const fieldSchema = this.shape[key]; - let newField = fieldSchema; - while (newField instanceof ZodOptional) { - newField = (newField as ZodOptional)._def.innerType; - } + if (mask) { + util.objectKeys(this.shape).map((key) => { + if (util.objectKeys(mask).indexOf(key) === -1) { + newShape[key] = this.shape[key]; + } else { + const fieldSchema = this.shape[key]; + let newField = fieldSchema; + while (newField instanceof ZodOptional) { + newField = (newField as ZodOptional)._def.innerType; + } + newShape[key] = newField; + } + }); + } else { + for (const key in this.shape) { + const fieldSchema = this.shape[key]; + let newField = fieldSchema; + while (newField instanceof ZodOptional) { + newField = (newField as ZodOptional)._def.innerType; + } - newShape[key] = newField; + newShape[key] = newField; + } } return new ZodObject({ ...this._def, diff --git a/src/__tests__/partials.test.ts b/src/__tests__/partials.test.ts index 5a57f4f36..35a28313c 100644 --- a/src/__tests__/partials.test.ts +++ b/src/__tests__/partials.test.ts @@ -146,7 +146,22 @@ test("required", () => { expect(requiredObject.shape.field).toBeInstanceOf(z.ZodDefault); }); -test("with mask", async () => { +test("required with mask", () => { + const object = z.object({ + name: z.string(), + age: z.number().optional(), + field: z.string().optional().default("asdf"), + country: z.string().optional(), + }); + + const requiredObject = object.required({ age: true }); + expect(requiredObject.shape.name).toBeInstanceOf(z.ZodString); + expect(requiredObject.shape.age).toBeInstanceOf(z.ZodNumber); + expect(requiredObject.shape.field).toBeInstanceOf(z.ZodDefault); + expect(requiredObject.shape.country).toBeInstanceOf(z.ZodOptional); +}); + +test("partial with mask", async () => { const object = z.object({ name: z.string(), age: z.number().optional(), diff --git a/src/types.ts b/src/types.ts index d32b5ecdc..6096bd289 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1915,16 +1915,41 @@ export class ZodObject< { [k in keyof T]: deoptional }, UnknownKeys, Catchall - > { + >; + required( + mask: Mask + ): ZodObject< + objectUtil.noNever<{ + [k in keyof T]: k extends keyof Mask ? deoptional : T[k]; + }>, + UnknownKeys, + Catchall + >; + required(mask?: any) { const newShape: any = {}; - for (const key in this.shape) { - const fieldSchema = this.shape[key]; - let newField = fieldSchema; - while (newField instanceof ZodOptional) { - newField = (newField as ZodOptional)._def.innerType; - } + if (mask) { + util.objectKeys(this.shape).map((key) => { + if (util.objectKeys(mask).indexOf(key) === -1) { + newShape[key] = this.shape[key]; + } else { + const fieldSchema = this.shape[key]; + let newField = fieldSchema; + while (newField instanceof ZodOptional) { + newField = (newField as ZodOptional)._def.innerType; + } + newShape[key] = newField; + } + }); + } else { + for (const key in this.shape) { + const fieldSchema = this.shape[key]; + let newField = fieldSchema; + while (newField instanceof ZodOptional) { + newField = (newField as ZodOptional)._def.innerType; + } - newShape[key] = newField; + newShape[key] = newField; + } } return new ZodObject({ ...this._def, From da5d352b1c71a50b3a2b6fcab823ed0be0e69295 Mon Sep 17 00:00:00 2001 From: Henrique Bruno Date: Fri, 5 Aug 2022 14:25:17 +0100 Subject: [PATCH 2/2] docs: add required and its mask info --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index 348056a14..693374ee6 100644 --- a/README.md +++ b/README.md @@ -925,6 +925,42 @@ const deepPartialUser = user.deepPartial(); > Important limitation: deep partials only work as expected in hierarchies of objects, arrays, and tuples. + +### `.required` + +Contrary to the `.partial` method, the `.required` method makes all properties required. + +Starting from this object: + +```ts +const user = z.object({ + email: z.string() + username: z.string(), +}).partial(); +// { email?: string | undefined; username?: string | undefined } +``` + +We can create a required version: + +```ts +const requiredUser = user.required(); +// { email: string; username: string } +``` + +You can also specify which properties to make required: + +```ts +const requiredEmail = user.required({ + email: true, +}); +/* +{ + email: string; + username?: string | undefined; +} +*/ +``` + ### `.passthrough` By default Zod object schemas strip out unrecognized keys during parsing.