Here you have an example:
// You can omit `test` property in Data interface since it has a string type
interface Data {
[key: string]: string,
foo?: boolean,
}
// You can use Verify helper instead of Data interface. It is almost the same
type VerifyT<T> = { foo?: boolean } & { [K in keyof T]: K extends "foo" ? unknown : string };
const make = <T extends VerifyT<T>>(t: T) => t;
make({ age: 'sdf', foo: true }) // Ok
make({ age: 'sdf', foo: undefined }) // ok
make({ age: 'sdf', foo: undefined }) // false
make({ age: 'sdf', foo: 'some text' }) // error
make({ age: 'sdf', foo: 1 }) // error
make({ age: 'sdf', foo: [1] }) // error
Don't worry about function overhead, because if you use V8 engine, it will be 99% inlined and optimized
All gredits goes to this answer.
Also feel free to marks this question as a dublicate