You can't do this automatically. TypeScript's static type system, including the definition of the Thing
type alias, gets erased when the code is transpiled to JavaScript. So there will be nothing left at runtime to act upon when you try to omit the keys of Thing
from obj
.
If you want to omit the keys of Thing
at runtime, you will need to explicitly create a list of such keys as a value that exists at runtime. With some effort you can get the compiler to at least check that you've done so correctly. For example:
const thingKeys = ["name"] as const;
type ThingKeysCheck<
T extends typeof thingKeys[number] = keyof Thing,
// --------------------------------> ~~~~~~~~~~~
// error here if thingKeys has missing keys
U extends keyof Thing = typeof thingKeys[number]
// -------------------> ~~~~~~~~~~~~~~~~~~~~~~~~
// error here if thingKeys has extra keys
> = void;
That const
assertion lets the compiler realize that thingKeys
contains the string literal "name"
(otherwise it would just see thingKeys
as a string[]
).
And ThingKeysCheck
is just there as a check to see if the string literal elements of thingKeys
are exactly keyof Thing
. If there are no errors in that line, then thingKeys
and Thing
are consistent with each other. Otherwise there will be an error mentioning whether keys are missing or extra or both.
Once you have thingKeys
, you can use it in your filter. The following withoutThing()
function has a generic call signature saying that if you pass in obj
of a type T
assignable to Thing
is passed in, then you will get a value of type Omit<T, keyof Thing>
using the Omit<T, K>
utility type:
function withoutThing<T extends Thing>(obj: T): Omit<T, keyof Thing> {
return Object
.keys(obj)
.filter(k => !(thingKeys as readonly string[]).includes(k))
.reduce<any>((o, k) => ({ ...o, [k]: obj[k as keyof Thing] }), {});
}
I did a lot of type assertions inside the implementation of that function to quiet the compiler complaints; it's not very good at verifying whether you are actually creating a valid object of type Omit<T, keyof Thing>
. Anyway, it's basically the same implementation as you have written.
Let's see if it works:
const objWithoutThing = withoutThing(obj);
console.log(objWithoutThing.someProp); // true
console.log(objWithoutThing.otherProp.toFixed(2)); // 123.00
console.log(objWithoutThing.yetAnotherProp.toUpperCase()); // "YAY"
console.log(objWithoutThing.name); // undefined
// -----------------------> ~~~~
// Property 'name' does not exist
Looks good to me.
Playground link to code