TypeScript: безопасный для типов способ преобразования между объектами и кортежами? - PullRequest
0 голосов
/ 01 мая 2020

В недавнем проекте у меня есть много классов, которые по сути называются кортежами, и мне часто приходится конвертировать эти объекты в кортежи. Это означает, что у меня в основном много функций, подобных этой:

const tupleToFoo = ([a, b, c]) => new Foo(a, b, c)
const fooToTuple = (foo) => [foo.a, foo.b, foo.c]

Возможно, мне следовало бы просто оставить все как есть, но я не мог не задаться вопросом, существует ли более обобщенный способ c написать этот код. Вот что я придумал:

type AnyConstructor<T> = { new (...args: any[]): T }

type KeysOf<T> = ReadonlyArray<keyof T>
type Lookup<T, K> = K extends keyof T ? T[K] : never
type UnionFromInterface<T, K extends KeysOf<T>> = T[K[number]]
type TupleFromInterface<T, K extends KeysOf<T>> = {
  [I in keyof K]: Lookup<T, K[I]>
}

function construct<T, K extends ReadonlyArray<keyof T>>(
  C: AnyConstructor<T>
): (t: UnionFromInterface<T, K>[]) => T {
  return t => new C(...t)
}

function deconstruct<T, K extends ReadonlyArray<keyof T>>(
  D: K
): (o: T) => UnionFromInterface<T, K>[] {
  return o => D.map(k => o[k])
}

function tupleToUnions<T, K extends ReadonlyArray<keyof T>>(
  t: TupleFromInterface<T, K>
): UnionFromInterface<T, K>[] {
  return t.map(i => i as UnionFromInterface<T, K>)
}

// This is particularly bad... :(
function unionsToTuple<T, K extends ReadonlyArray<keyof T>>(
  u: UnionFromInterface<T, K>[]
): TupleFromInterface<T, K> {
  return (u as unknown) as TupleFromInterface<T, K>
}

function instanceToTuple<T, K extends ReadonlyArray<keyof T>>(
  T: (o: T) => UnionFromInterface<T, K>[]
): (o: T) => TupleFromInterface<T, K> {
  return o => unionsToTuple(T(o))
}

function tupleToInstance<T, K extends ReadonlyArray<keyof T>>(
  O: (t: UnionFromInterface<T, K>[]) => T
): (t: TupleFromInterface<T, K>) => T {
  return t => O(tupleToUnions(t))
}

Что можно использовать следующим образом:

class Person {
  static Keys = ['name', 'hobbies'] as const

  name: string
  hobbies?: string[]

  constructor(name: string, hobbies?: string[]) {
    this.name = name
    this.hobbies = hobbies
  }
}

const tupleToPerson = tupleToInstance(
  construct<Person, typeof Person.Keys>(Person)
)
const personToTuple = instanceToTuple(
  deconstruct<Person, typeof Person.Keys>(Person.Keys)
)

const p = new Person('John Doe', ['yodeling'])

const t = personToTuple(p)
const q = tupleToPerson(t)

console.log(p, t, q)

Хотя это «работает», проблема в том, что это совсем не безопасно. Такое ощущение, что должен быть безопасный для этого способ, поскольку вся необходимая информация доступна во время компиляции. Следовательно, мне было любопытно, знает ли кто-нибудь безопасный для типа способ реализации чего-то подобного?

...