Home > Software engineering >  Generically extract a value from typed Record
Generically extract a value from typed Record

Time:03-10

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

Playground Link

  • Related