Is it possible to make a mapped type property optional conditionally?
Consider this type
type Definition {
name: string,
defaultImplementation?: ImplementationType
}
and a record of them:
type DefinitionMap = Record<string, Definition>
I would like to make a mapped type that has an implementation that is optional if the input is provided, but the mapped type implementation required if it wasn't.
For an DefinitionMap
like this
{
foo: { name: 'x' },
bar: { name: 'y', defaultImplementation: { /*...*/ } }
}
I would like to have a mapped type like
{
foo: ImplementationType,
bar?: ImplementationType
}
I've been trying to use conditionals and add undefined
to the type, but that is not working.
type ImplementationMap<T extends DefinitionMap> = {
[K in keyof T]: T[K] extends { defaultImplementation: any }
? ImplementationType | undefined
: ImplementationType
}
I know that the conditional branches behave how I want them to, but adding undefined
doesn't actually make the field optional.
Here's a solution:
type NonImplementedKeys<T extends DefinitionMap> = {[K in keyof T]: T[K] extends {defaultImplementation: ImplementationType} ? never : K}[keyof T]
type NiceIntersection<S, T> = {[K in keyof (S & T)]: (S & T)[K]}
type ImplementationMap<T extends DefinitionMap> = NiceIntersection<{
[K in NonImplementedKeys<T>]: ImplementationType
}, {
[K in keyof T]?: ImplementationType
}>
Example:
type DefinitionMapExample = {
foo: { name: 'x' },
bar: { name: 'y', defaultImplementation: { /*...*/ } }
}
// {foo: ImplementationType, bar?: ImplementationType | undefined}
type ImplementationMapExample = ImplementationMap<DefinitionMapExample>
The NiceIntersection<S, T>
type is equivalent to a plain intersection type S & T
, except it makes the result look like {foo: ..., bar?: ...}
instead of {foo: ...} & {bar?: ...}
.