To implement compile-time checking for a scenario like this, you can create a type called KeysFrom<T, U>
. This type ensures that the keys of one object are a subset of another.
type KeysFrom<T, U> = { [K in keyof U]: K extends keyof T ? U[K] : never }
By using
KeysFrom<{a: string}, {a: number}>
, you will see that it correctly resolves to
{a: number}
. However, with
KeysFrom<{a: string}, {b: number}
, it results in
{b: never}
. You can then assert that
ThingOptions
extends
KeysFrom<Thing, ThingOptions>
.
interface ThingOptions extends KeysFrom<Thing, ThingOptions> {
foo: number;
bar: string;
}
This approach works effectively by enforcing the constraint at compile time. If there is an incompatible property, such as in BadThingOptions
, it will fail compilation.
Another method involves creating a separate type checker:
type KeysAreSubset<
T,
U extends { [K in keyof U]: K extends keyof T ? U[K] : never }
> = true;
With this technique, you introduce the check in its own line and ensure that any type U
must conform to KeysFrom<T, U>
. This way, any mismatch between keys will trigger an error during compilation.
Both these strategies provide compile-time validation, albeit requiring slight alterations to the original name and some additional code complexity. Nonetheless, they offer robust solutions based on your specific requirements. Best of luck implementing them!
Code Link