Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript Type-safe Omit Function

I want to replicate lodash's _.omit function in plain typescript. omit should return an object with certain properties removed specified via parameters after the object parameter which comes first.

Here is my best attempt:

function omit<T extends object, K extends keyof T>(obj: T, ...keys: K[]): {[k in Exclude<keyof T, K>]: T[k]} {
    let ret: any = {};
    let key: keyof T;
    for (key in obj) {
        if (!(keys.includes(key))) {
            ret[key] = obj[key];
        }
    }
    return ret;
}

Which gives me this error:

Argument of type 'keyof T' is not assignable to parameter of type 'K'.
  Type 'string | number | symbol' is not assignable to type 'K'.
    Type 'string' is not assignable to type 'K'.ts(2345)
let key: keyof T

My interpretation of the error is that:

  1. Since key is a keyof T and T is an object, key can be a symbol, number or string.

  2. Since I use the for in loop, key can only be a string but includes might take a number if I pass in an array, for example? I think. So that means there's a type error here?

Any insights as to why this doesn't work and how to make it work are appreciated!

like image 782
Salami Avatar asked Dec 29 '18 03:12

Salami


1 Answers

interface Omit {
    <T extends object, K extends [...(keyof T)[]]>
    (obj: T, ...keys: K): {
        [K2 in Exclude<keyof T, K[number]>]: T[K2]
    }
}

const omit: Omit = (obj, ...keys) => {
    const ret = {} as {
        [K in keyof typeof obj]: (typeof obj)[K]
    };
    let key: keyof typeof obj;
    for (key in obj) {
        if (!(keys.includes(key))) {
            ret[key] = obj[key];
        }
    }
    return ret;
};

For convenience I've pulled most of the typings to an interface.

The problem was that K had been being inferred as a tuple, not as a union of keys. Hence, I changed it's type constraint accordingly:

[...(keyof T)[]] // which can be broke down to:
keyof T // a union of keys of T
(keyof T)[] // an array containing keys of T
[...X] // a tuple that contains X (zero or more arrays like the  described one above)

Then, we need to transform the tuple K to a union (in order to Exclude it from keyof T). It is done with K[number], which is I guess is self-explaining, it's the same as T[keyof T] creating a union of values of T.

Playground

like image 90
Nurbol Alpysbayev Avatar answered Sep 17 '22 12:09

Nurbol Alpysbayev