There is no automatic way to achieve this. When TypeScript code is transpiled to JavaScript, the static type system and type aliases like Thing
get erased. Consequently, at runtime, there won't be any information available to omit keys from obj
based on Thing
.
To exclude keys of Thing
during runtime, you must explicitly define a list of such keys as a value that remains present at runtime. By putting in some effort, you can make the compiler check if the list is created correctly:
const thingKeys = ["name"] as const;
type ThingKeysCheck<
T extends typeof thingKeys[number] = keyof Thing,
U extends keyof Thing = typeof thingKeys[number]
> = void;
The use of const
assertion ensures that the compiler recognizes thingKeys
contains the string literal "name"
. Without it, thingKeys
would simply be seen as an array of strings.
ThingKeysCheck
serves as a validation to confirm the consistency between the string literals in thingKeys
and keyof Thing
. Any errors will indicate missing or extra keys in either object.
Once you have defined thingKeys
, you can apply it in your filter logic. The function withoutThing()
uses a generic signature to ensure that passing an object T
assignable to
Thing</code results in a return type of <code>Omit<T, keyof Thing>
using
the utility type Omit<T, K>
:
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] }), {});
}
Several type assertions are used within the function implementation to address compiler warnings about creating a valid object of type Omit<T, keyof Thing>
. Overall, it maintains similar functionality to your original implementation.
Let's test its functionality:
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
Seems to be working well.
Playground link containing the code