You can't do it that way around, because TypeScript type information isn't available at runtime. You have to have a runtime array, ["foo", "bar"]
, in order to implement your type predicate. But that would seem to mean having the names in two places, which is a maintenance concern.
When I'm in this situation, I usually turn it on its head and create the runtime array, then define both the type and the type predicate in terms of the array, like this:
const fooBarKeys = ["foo", "bar"] as const;
type TypeHelper<Keys extends PropertyKey> = {
[Key in Keys]: number;
};
type FooBar = TypeHelper<typeof fooBarKeys[number]>;
// ^? type FooBar = { foo: number; bar: number; }
function isFooBar(obj: any): obj is FooBar {
for (const key of fooBarKeys) {
if (typeof obj[key] !== "number") {
return false;
}
}
return true;
}
Example use of the type predicate:
console.log(isFooBar({foo: 42, bar: 67})); // true
console.log(isFooBar({foo: 42})); // false
Playground link
You can also do it by using a "model object," which is just a different approach to the same concept of building the type information from data structures that are also available at runtime:
const fooBarModel = {
foo: 42,
bar: 67,
};
type FooBar = typeof fooBarModel;
// ^? type FooBar = { foo: number; bar: number; }
const fooBarKeys = Object.keys(fooBarModel) as Array<keyof FooBar>;
function isFooBar(obj: any): obj is FooBar {
for (const key of fooBarKeys) {
if (typeof obj[key] !== typeof fooBarModel[key]) { // This is imperfect, but better than nothing
return false;
}
}
return true;
}
console.log(isFooBar({foo: 42, bar: 67})); // true
console.log(isFooBar({foo: 42})); // false
Playground link