TypeScript Type-safe Omit Function

旧巷老猫 提交于 2019-12-04 13:05:01

问题


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!


回答1:


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) => {
    let 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 has been being (correct tense?) 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
[] // a tuple
[...X] // a tuple that contains an array X

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




回答2:


If we limit the type of keys to string [],It works. But it does not seem to be a good idea.Keys should be string | number | symbol[];

function omit<T, K extends string>(
  obj: T,
  ...keys: K[]
): { [k in Exclude<keyof T, K>]: T[k] } {
  let ret: any = {};
  Object.keys(obj)
    .filter((key: K) => !keys.includes(key))
    .forEach(key => {
      ret[key] = obj[key];
    });
  return ret;
}
const result = omit({ a: 1, b: 2, c: 3 }, 'a', 'c');
// The compiler inferred result as 
// {
//   b: number;
// }


来源:https://stackoverflow.com/questions/53966509/typescript-type-safe-omit-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!