Search code examples
typescript

Omit seems broken on type extending Record


Something unexpected happen when I use Omit to get rid of the member of source interface for extension.

If I extend A without omit, both a1 and a2 props are required. But I want to have a2 not present in B but inherit all other members from type A.

When I use Omit, somehow even a1 become optional, but I expect to a1 stay required.

interface A extends Record<string, any> {
    a1: string;
    a2: number;
}

interface B extends Omit<A, 'a2'>{
    b1: string;
    b2: number;
}

const example: B = {
    // a1: 'dfdf', // should throw error, a1 is required
    b1: 'string',
    b2: 12,
}

ts playground

Thanks for any hints 🙏


Solution

  • You can use key remapping to implement your own version of the Omit<T, K> utility type that respects index signatures, as described in microsoft/TypeScript#49656:

    type Oops = Omit<A, "a2">;
    /* type Oops = {
        [x: string]: any;
        [x: number]: any;
    } // ☹ */
    
    type MyOmit<T, K extends PropertyKey> =
        { [P in keyof T as Exclude<P, K>]: T[P] }
    
    type Okay = MyOmit<A, "a2">;
    /* type Okay = {
        [x: string]: any;
        a1: string;
    } // 🙂 */
    

    The Omit<T, K> utility type was introduced before key remapping was available.
    There is an issue at microsoft/TypeScript#41383 suggesting that key remapping be used instead, but it's unlikely to happen in production (see this comment on ms/TS#49656).

    Generally speaking once a utility type is introduced, it's not easy to redefine it without breaking real world code. Indeed there are several "problems" with Omit that can't be addressed; index signatures is one issue, and distributing over unions is another (e.g., people expect Omit<T | U, K> to be equivalent to Omit<T, K> | Omit<U, K>, but it isn't). See microsoft/TypeScript#53169 for discussion of a potential fix for union distribution, along with all the problems that arise when trying to use it. All this means: if a TypeScript-provided utility type doesn't behave as you think it should, you should write your own utility type that does.

    Playground link to code