I am using fp-ts and in this case, Options. Lets say I have an example type:
import * as O from 'fp-ts/Option';
type ExampleType = {
a: number,
b: O.Option<number>,
c: O.Option<string>
}
and a record of these types:
type ExampleRecord = Record<string, ExampleType>
I have discovered the same "theme" several times: I want to write a generic function that can, given a property name and the record type (which can be ExampleType or any other type with at least an option property), turns this:
const a: ExampleRecord = { key1: {a: 1, b: O.none, c: O.some('example')}, key2: { a: 2, b: O.some(3), c: O.none } }
into this:
const b = f('b', a) // b is { key2: { a: 2, b: 3, c: O.none } }
Notice I give it the b key and it filters all records that have no value for b, while extracting the value from the option in case it exists and returning it.
However, I have no idea how to type such a function f without losing the type information. The return type should be something like this:
type TheReturnType = Record<string, ExampleType & {b: number}>
But I fail to figure out how to infer this dynamically if it is even possible. I mean, the b: number comes from the fact that b is an O.Option in T, but as b is dynamic, that makes it harder to actually infer anything.
Does anyone have any ideas?
CodePudding user response:
You can extract the type of the option using a conditional type. You can then use a generic function to capture the type of the record type (lets call it T
), and the type of the key (lets call it K
). With this information you can then create the required type, by omitting K
from T
and intersecting it with the an object type that contains just K
with the type of the option:
const a: ExampleRecord = { key1: { a: 1, b: O.none, c: O.some('example') }, key2: { a: 2, b: O.some(3), c: O.none } }
type OptionValue<T extends O.Option<any>> = T extends O.Some<infer U> ? U: never;
function f<T extends Record<K, O.Option<any>>, K extends keyof T>(key: K, value: Record<string, T>): Record<string, Omit<T, K> & Record<K, OptionValue<T[K]>>> {
return null!;
}
const b = f('b', a) // b is { key2: { a: 2, b: 3, c: O.none }
b["key2"].b.toExponential // number