type User = {
name?: string;
age?: number;
sex?: string;
};
type MyOmit<T, K extends PropertyKey> = { [P in Exclude<keyof T, K>]: T[P] };
type A = MyOmit<User, "name" | "age">;
type B = Omit<User, "name" | "age">;
Why type A = {sex:string|undefined}
and type B = {sex?:string|undefined}
? The underlying processing method of Omit type tools is MyOmit. Why do the results differ?
You encountered a TypeScript behavior known as (non-)homomorphic type mapping. In short, homomorphic mapping means that property modifiers such as readonly
and optional (?
) are preserved on the result of the mapped type.
Check out this famous question for more information: What does "homomorphic mapped type" mean?
A homomorphic mapped type is created when the compiler recognizes that the type to be mapped is an existing object type (=User
in your example). That commonly happens when you e. g. use the in keyof
syntax or in K
where K extends keyof T
and T
is the object type to be mapped.
On the other hand non-homomorphic mapping is applied when the compiler does NOT recognize your existing object type. This is the case when introducing other conditional types like Exclude
or Extract
. Therefore, TypeScript can't see K
is actually related to User
.
As noted in microsoft/TypeScript/pull/12563 you can wrap MyOmit
in a Pick
for a workaround solution:
type User = {
name?: string;
age?: number;
sex?: string;
};
type MyOmit<T, K extends PropertyKey> = Pick<
T,
keyof { [P in Exclude<keyof T, K>]: T[P] }
>;
type A = MyOmit<User, "name" | "age">;
// ^? type A = { sex?: string | undefined; }