Pesquisa de site

typescript - Extrair genericamente um valor de um Record tipado


Estou usando fp-ts e, neste caso, Options. Digamos que eu tenha um tipo de exemplo:

import * as O from 'fp-ts/Option';

type ExampleType = {
  a: number,
  b: O.Option<number>,
  c: O.Option<string>
}

e um registro desses tipos:

type ExampleRecord = Record<string, ExampleType>

Descobri o mesmo "tema" várias vezes: quero escrever uma função genérica que possa, dado um nome de propriedade e o tipo de registro (que pode ser ExampleType ou qualquer outro tipo com pelo menos uma propriedade opcional), transformar isto:

const a: ExampleRecord = { key1: {a: 1, b: O.none, c: O.some('example')}, key2: { a: 2, b: O.some(3), c: O.none } }

nisto:

const b = f('b', a) // b is { key2: { a: 2, b: 3, c: O.none } } 

Note que eu dou a ela a chave b e ela filtra todos os registros que não têm valor para b, enquanto extrai o valor da opção caso exista e o retorna.

No entanto, não tenho ideia de como digitar tal função f sem perder a informação do tipo. O tipo de retorno deveria ser algo assim:

type TheReturnType = Record<string, ExampleType & {b: number}>

Mas eu não consigo entender como inferir isso dinamicamente, se é que isso é possível. Quero dizer, o b: número vem do fato de que b é um O.Option em T, mas como b é dinâmico, isso torna mais difícil realmente inferir qualquer coisa.

Alguém tem alguma ideia?

Resposta #1

Você pode extrair o tipo da opção usando um tipo condicional. Você pode então usar uma função genérica para capturar o tipo do tipo de registro (vamos chamá-lo de T) e o tipo da chave (vamos chamá-lo de K). Com essas informações, você pode então criar o tipo necessário, omitindo K de T e intersectando-o com um tipo de objeto que contém apenas K com o tipo da opção:

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

Link do Playground