Skip to content

Commit

Permalink
Improve addQuestionMarks, fix #2184 (#3352)
Browse files Browse the repository at this point in the history
* Improve addQuestionMarks, fix #2184

* Fix #2196

* Fix

* Add test case
  • Loading branch information
colinhacks committed Mar 22, 2024
1 parent b941914 commit 0269910
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 14 deletions.
16 changes: 16 additions & 0 deletions deno/lib/__tests__/generics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,19 @@ test("generics", () => {
const result = stripOuter(z.object({ a: z.string() }), { a: "asdf" });
util.assertEqual<typeof result, Promise<{ a: string }>>(true);
});

test("assignability", () => {
const createSchemaAndParse = <K extends string, VS extends z.ZodString>(
key: K,
valueSchema: VS,
data: unknown
) => {
const schema = z.object({
[key]: valueSchema,
});
const parsed = schema.parse(data);
const inferred: z.infer<z.ZodObject<{ [k in K]: VS }>> = parsed;
return inferred;
};
createSchemaAndParse("foo", z.string(), { foo: "" });
});
20 changes: 20 additions & 0 deletions deno/lib/__tests__/object.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,3 +451,23 @@ test("passthrough index signature", () => {
type b = z.infer<typeof b>;
util.assertEqual<{ a: string } & { [k: string]: unknown }, b>(true);
});

test("xor", () => {
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = T extends object
? U extends object
? (Without<T, U> & U) | (Without<U, T> & T)
: U
: T;

type A = { name: string; a: number };
type B = { name: string; b: number };
type C = XOR<A, B>;
type Outer = { data: C };
const Outer: z.ZodType<Outer> = z.object({
data: z.union([
z.object({ name: z.string(), a: z.number() }),
z.object({ name: z.string(), b: z.number() }),
]),
});
});
15 changes: 10 additions & 5 deletions deno/lib/helpers/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,23 @@ export namespace objectUtil {
[k in Exclude<keyof U, keyof V>]: U[k];
} & V;

// type optionalKeys<T extends object> = {
// [k in keyof T]: undefined extends T[k] ? k : never;
// }[keyof T];
type optionalKeys<T extends object> = {
[k in keyof T]: undefined extends T[k] ? k : never;
}[keyof T];

type requiredKeys<T extends object> = {
[k in keyof T]: undefined extends T[k] ? never : k;
}[keyof T];

// export type addQuestionMarks<
// T extends object,
// R extends keyof T = requiredKeys<T>
// > = Pick<Required<T>, R> & Partial<T>;
export type addQuestionMarks<
T extends object,
R extends keyof T = requiredKeys<T>
> = Pick<Required<T>, R> & Partial<T>;
R extends keyof T = requiredKeys<T>,
O extends keyof T = optionalKeys<T>
> = Pick<T, R> & Partial<Pick<T, O>> & { [k in keyof T]?: unknown };

export type identity<T> = T;
export type flatten<T> = identity<{ [k in keyof T]: T[k] }>;
Expand Down
5 changes: 1 addition & 4 deletions playground.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { z, ZodNativeEnum } from "./src";
import { z } from "./src";

z;

const A = z.object({}).catchall(z.string());
type A = z.infer<typeof A>;
16 changes: 16 additions & 0 deletions src/__tests__/generics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,19 @@ test("generics", () => {
const result = stripOuter(z.object({ a: z.string() }), { a: "asdf" });
util.assertEqual<typeof result, Promise<{ a: string }>>(true);
});

test("assignability", () => {
const createSchemaAndParse = <K extends string, VS extends z.ZodString>(
key: K,
valueSchema: VS,
data: unknown
) => {
const schema = z.object({
[key]: valueSchema,
});
const parsed = schema.parse(data);
const inferred: z.infer<z.ZodObject<{ [k in K]: VS }>> = parsed;
return inferred;
};
createSchemaAndParse("foo", z.string(), { foo: "" });
});
20 changes: 20 additions & 0 deletions src/__tests__/object.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,3 +450,23 @@ test("passthrough index signature", () => {
type b = z.infer<typeof b>;
util.assertEqual<{ a: string } & { [k: string]: unknown }, b>(true);
});

test("xor", () => {
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
type XOR<T, U> = T extends object
? U extends object
? (Without<T, U> & U) | (Without<U, T> & T)
: U
: T;

type A = { name: string; a: number };
type B = { name: string; b: number };
type C = XOR<A, B>;
type Outer = { data: C };
const Outer: z.ZodType<Outer> = z.object({
data: z.union([
z.object({ name: z.string(), a: z.number() }),
z.object({ name: z.string(), b: z.number() }),
]),
});
});
15 changes: 10 additions & 5 deletions src/helpers/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,23 @@ export namespace objectUtil {
[k in Exclude<keyof U, keyof V>]: U[k];
} & V;

// type optionalKeys<T extends object> = {
// [k in keyof T]: undefined extends T[k] ? k : never;
// }[keyof T];
type optionalKeys<T extends object> = {
[k in keyof T]: undefined extends T[k] ? k : never;
}[keyof T];

type requiredKeys<T extends object> = {
[k in keyof T]: undefined extends T[k] ? never : k;
}[keyof T];

// export type addQuestionMarks<
// T extends object,
// R extends keyof T = requiredKeys<T>
// > = Pick<Required<T>, R> & Partial<T>;
export type addQuestionMarks<
T extends object,
R extends keyof T = requiredKeys<T>
> = Pick<Required<T>, R> & Partial<T>;
R extends keyof T = requiredKeys<T>,
O extends keyof T = optionalKeys<T>
> = Pick<T, R> & Partial<Pick<T, O>> & { [k in keyof T]?: unknown };

export type identity<T> = T;
export type flatten<T> = identity<{ [k in keyof T]: T[k] }>;
Expand Down

0 comments on commit 0269910

Please sign in to comment.