Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ZodObject pick/omit/required/partial strict args keys #2716

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions deno/lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,7 @@ z.string().regex(regex);
z.string().includes(string);
z.string().startsWith(string);
z.string().endsWith(string);
z.string().datetime(); // defaults to UTC, see below for options
z.string().datetime(); // ISO 8601; default is without UTC offset, see below for options
z.string().ip(); // defaults to IPv4 and IPv6, see below for options

// transformations
Expand Down Expand Up @@ -760,7 +760,7 @@ z.string().ip({ message: "Invalid IP address" });

### ISO datetimes

The `z.string().datetime()` method defaults to UTC validation: no timezone offsets with arbitrary sub-second decimal precision.
The `z.string().datetime()` method enforces ISO 8601; default is no timezone offsets and arbitrary sub-second decimal precision.

```ts
const datetime = z.string().datetime();
Expand Down Expand Up @@ -2825,10 +2825,9 @@ This more declarative API makes schema definitions vastly more concise.

[https://github.com/pelotom/runtypes](https://github.com/pelotom/runtypes)

Good type inference support. They DO support readonly types, which Zod does not.
Good type inference support.

- Supports "pattern matching": computed properties that distribute over unions
- Supports readonly types
- Missing object methods: (deepPartial, merge)
- Missing nonempty arrays with proper typing (`[T, ...T[]]`)
- Missing promise schemas
Expand Down
18 changes: 18 additions & 0 deletions deno/lib/__tests__/object.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,3 +451,21 @@ test("passthrough index signature", () => {
type b = z.infer<typeof b>;
util.assertEqual<{ a: string } & { [k: string]: unknown }, b>(true);
});

test("error when pick/omit/required/partial with unknown keys", () => {
const example = z.object({
name: z.string(),
age: z.number(),
isAdmin: z.boolean(),
});

type BadExample = util.StrictKnownKeys<
z.infer<typeof example>,
{ name: true; asd: true; zxc: false; xcv: "asd" }
>;

util.assertEqual<
BadExample,
{ name: true; asd: never; zxc: never; xcv: never }
>(true);
});
4 changes: 4 additions & 0 deletions deno/lib/helpers/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export namespace util {
throw new Error();
}

export type StrictKnownKeys<TObj, TKeysObj> = {
[k in keyof TKeysObj]: k extends keyof TObj ? TKeysObj[k] : never;
};

export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type OmitKeys<T, K extends string> = Pick<T, Exclude<keyof T, K>>;
export type MakePartial<T, K extends keyof T> = Omit<T, K> &
Expand Down
8 changes: 4 additions & 4 deletions deno/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2566,7 +2566,7 @@ export class ZodObject<
}

pick<Mask extends { [k in keyof T]?: true }>(
mask: Mask
mask: util.StrictKnownKeys<T, Mask>
): ZodObject<Pick<T, Extract<keyof T, keyof Mask>>, UnknownKeys, Catchall> {
const shape: any = {};

Expand All @@ -2583,7 +2583,7 @@ export class ZodObject<
}

omit<Mask extends { [k in keyof T]?: true }>(
mask: Mask
mask: util.StrictKnownKeys<T, Mask>
): ZodObject<Omit<T, keyof Mask>, UnknownKeys, Catchall> {
const shape: any = {};

Expand Down Expand Up @@ -2612,7 +2612,7 @@ export class ZodObject<
Catchall
>;
partial<Mask extends { [k in keyof T]?: true }>(
mask: Mask
mask: util.StrictKnownKeys<T, Mask>
): ZodObject<
objectUtil.noNever<{
[k in keyof T]: k extends keyof Mask ? ZodOptional<T[k]> : T[k];
Expand Down Expand Up @@ -2645,7 +2645,7 @@ export class ZodObject<
Catchall
>;
required<Mask extends { [k in keyof T]?: true }>(
mask: Mask
mask: util.StrictKnownKeys<T, Mask>
): ZodObject<
objectUtil.noNever<{
[k in keyof T]: k extends keyof Mask ? deoptional<T[k]> : T[k];
Expand Down
18 changes: 18 additions & 0 deletions src/__tests__/object.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,3 +450,21 @@ test("passthrough index signature", () => {
type b = z.infer<typeof b>;
util.assertEqual<{ a: string } & { [k: string]: unknown }, b>(true);
});

test("error when pick/omit/required/partial with unknown keys", () => {
const example = z.object({
name: z.string(),
age: z.number(),
isAdmin: z.boolean(),
});

type BadExample = util.StrictKnownKeys<
z.infer<typeof example>,
{ name: true; asd: true; zxc: false; xcv: "asd" }
>;

util.assertEqual<
BadExample,
{ name: true; asd: never; zxc: never; xcv: never }
>(true);
});
4 changes: 4 additions & 0 deletions src/helpers/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export namespace util {
throw new Error();
}

export type StrictKnownKeys<TObj, TKeysObj> = {
[k in keyof TKeysObj]: k extends keyof TObj ? TKeysObj[k] : never;
};

export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type OmitKeys<T, K extends string> = Pick<T, Exclude<keyof T, K>>;
export type MakePartial<T, K extends keyof T> = Omit<T, K> &
Expand Down
8 changes: 4 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2566,7 +2566,7 @@ export class ZodObject<
}

pick<Mask extends { [k in keyof T]?: true }>(
mask: Mask
mask: util.StrictKnownKeys<T, Mask>
): ZodObject<Pick<T, Extract<keyof T, keyof Mask>>, UnknownKeys, Catchall> {
const shape: any = {};

Expand All @@ -2583,7 +2583,7 @@ export class ZodObject<
}

omit<Mask extends { [k in keyof T]?: true }>(
mask: Mask
mask: util.StrictKnownKeys<T, Mask>
): ZodObject<Omit<T, keyof Mask>, UnknownKeys, Catchall> {
const shape: any = {};

Expand Down Expand Up @@ -2612,7 +2612,7 @@ export class ZodObject<
Catchall
>;
partial<Mask extends { [k in keyof T]?: true }>(
mask: Mask
mask: util.StrictKnownKeys<T, Mask>
): ZodObject<
objectUtil.noNever<{
[k in keyof T]: k extends keyof Mask ? ZodOptional<T[k]> : T[k];
Expand Down Expand Up @@ -2645,7 +2645,7 @@ export class ZodObject<
Catchall
>;
required<Mask extends { [k in keyof T]?: true }>(
mask: Mask
mask: util.StrictKnownKeys<T, Mask>
): ZodObject<
objectUtil.noNever<{
[k in keyof T]: k extends keyof Mask ? deoptional<T[k]> : T[k];
Expand Down