Typescript Type-safe Omit Function
Solution 1:
interfaceOmit {
<T extendsobject, K extends [...(keyof T)[]]>
(obj: T, ...keys: K): {
[K2inExclude<keyof T, K[number]>]: T[K2]
}
}
constomit: Omit = (obj, ...keys) => {
const ret = {} as {
[K in keyof typeof obj]: (typeof obj)[K]
};
letkey: 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
.
Solution 2:
The accepted answer from Nurbol above is probably the more typed version, but here is what I am doing in my utils-min
.
It uses the typescript built-in Omit and is designed to only support string key names. (still need to loosen up the Set to Set, but everything else seems to work nicely)
exportfunction omit<T extendsobject, K extendsExtract<keyof T, string>>(obj: T, ...keys: K[]): Omit<T, K> {
letret: any = {};
constexcludeSet: Set<string> = newSet(keys);
// TS-NOTE: Set<K> makes the obj[key] type check fail. So, loosing typing here. for (let key in obj) {
if (!excludeSet.has(key)) {
ret[key] = obj[key];
}
}
return ret;
}
Solution 3:
Object.keys
or for in
returns keys as string and excludes symbols. Numeric keys are also converted to strings.
You need to convert numeric string keys to numbers otherwise it will return the object with string keys.
function omit<T extendsRecord<string | number, T['']>,
K extends [...(keyof T)[]]>(
obj: T,
...keys: K
): { [P inExclude<keyof T, K[number]>]: T[P] } {
return (Object.keys(obj)
.map((key) =>convertToNumbers(keys, key)) asArray<keyof T>)
.filter((key) => !keys.includes(key))
.reduce((agg, key) => ({ ...agg, [key]: obj[key] }), {}) as {
[P inExclude<keyof T, K[number]>]: T[P];
};
}
functionconvertToNumbers(
keys: Array<string | number | symbol>,
value: string | number
): number | string {
if (!isNaN(Number(value)) && keys.some((v) => v === Number(value))) {
returnNumber(value);
}
return value;
}
// without converToNumbers omit({1:1,2:'2'}, 1) will return {'1':1, '2':'2'}// Specifying a numeric string instead of a number will fail in Typescript
To include symbols you can use the code below.
function omit<T, K extends [...(keyof T)[]]>(
obj: T,
...keys: K
): { [P inExclude<keyof T, K[number]>]: T[P] } {
return (Object.getOwnPropertySymbols(obj) asArray<keyof T>)
.concat(Object.keys(obj)
.map((key) =>convertToNumbers(keys, key)) asArray<keyof T>)
.filter((key) => !keys.includes(key))
.reduce((agg, key) => ({ ...agg, [key]: obj[key] }), {}) as {
[P inExclude<keyof T, K[number]>]: T[P];
};
}
Solution 4:
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 extendsstring>(
obj: T,
...keys: K[]
): { [k inExclude<keyof T, K>]: T[k] } {
letret: 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;// }
Solution 5:
Simplest way:
exportconst omit = <T extends object, K extends keyof T>(obj: T, ...keys: K[]): Omit<T, K> => {
keys.forEach((key) =>delete obj[key])
return obj
}
As a pure function:
exportconst omit = <T extends object, K extends keyof T>(obj: T, ...keys: K[]): Omit<T, K> => {
const _ = { ...obj }
keys.forEach((key) =>delete _[key])
return _
}
Post a Comment for "Typescript Type-safe Omit Function"