Imagine you have a code snippet like this
type ResourceDecorator = (input: UserResourceDefinition) => DecoratedResourceDefinition
const decorate: ResourceDecorator = ...
const resources = decorate({
Book1: {
resourceName: 'my-book',
resourceType: 'book'
},
Pencil1: {
resourceName: 'my-pencil',
resourceType: 'pencil'
}
})
The goal is to create the function decorate(...)
in such a way that the first-level keys from the input type (Book1
, Pencil1
) are also present in the output type. This allows using the output resources
as shown below
// somewhere else
console.log(resources.Book1.resourceName)
// Additional programmatically defined properties can be accessed along with user definition.
console.log(resources.Book1.exampleDecorationProperty)
An attempt was made using indexable object syntax, but it did not yield the desired result.
export interface UserResourceDefinition {
[key: string]: {
resourceName: string,
resourceType: string,
}
}
export interface DecoratedResourceDefinition {
[key: string]: {
resourceName: string,
resourceType: string,
exampleDecorationProperty: string
}
}
type ResourceDecorator = (input: UserResourceDefinition) => DecoratedResourceDefinition
const decorate: ResourceDecorator = (input) => {
return Object.entries(definition).map(([resourceKey, userDef]) => ({
resourceName: userDef.resourceName,
resourceType: userDef.resourceType,
resourceKey: resourceKey,
exampleDecorationProperty: someFunction(userDef)
})).reduce((accumObj, decoratedDef) => ({ ...accumObj, [decoratedDef.resourceKey]: decoratedDef }), {});
}
This approach fails because the output type of resources
does not recognize properties like Book1
and Pencil1
.
// somewhere else
// Auto completion cannot infer 'resources.Book1'
console.log(resources.Book1.resourceName)
// Compiler doesn't flag non-existing property 'Foo'
console.log(resources.Foo.resourceName)
Is there a way to achieve this functionality in Typescript?