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
