Reputation: 1060
I'm trying to validate an object literal's values, and at the same time create a union of all the keys as a parameter type to a function.
interface PathValue {
prop1: string;
prop2?: number;
}
interface PathDeclaration {
[key: string]: PathValue;
}
const allPaths: PathDeclaration = {
'one': {prop1: 'foo'},
'two': {prop1: 'bar', prop2: 2},
}
function getItem(path: Extract<keyof typeof allPaths, string>) {
return allPaths[path];
}
The problem with this example is that since PathDeclaration is the type of allPaths, and its keys are just generic strings, my path
parameter can't infer the keys from the object literal anymore.
The only solutions I can think of is to have to declare all the keys in a separate interface (duplicate code), or instead of typing allPaths, I could remove PathDeclaration
and just type each value like 'one': <PathValue> {prop1: 'foo'},
neither which are very elegant solutions. Is there a way to validate the entire allPaths
object AND create a key union type from the object literal?
Upvotes: 0
Views: 131
Reputation: 74500
You want to initialize const allPaths
with a generic function to 1) add the type constraint PathDeclaration
and 2) let TS infer the given object literal type automatically. Only functions can do this in one step.
const createPaths = <T extends PathDeclaration>(pd: T) => pd
const allPaths = createPaths({
'one': { prop1: 'foo' },
'two': { prop1: 'bar', prop2: 2 },
})
/*
{
one: { prop1: string; };
two: { prop1: string; prop2: number; };
}
*/
Above will catch errors:
const allPathsError = createPaths({
'one': { prop3: 'foo' }, // error
'two': { prop1: 'bar', prop2: "bar" }, // error
})
And infer all keys:
// path: "one" | "two"
function getItem(path: Extract<keyof typeof allPathsInline, string>) {
return allPathsInline[path];
}
This is also one of the few cases where I like to use an IIFE to make things lean:
const allPathsInline = (<T extends PathDeclaration>(pd: T) => pd)({
'one': { prop1: 'foo' },
'two': { prop1: 'bar', prop2: 2 },
})
Upvotes: 2