В недавнем проекте у меня есть много классов, которые по сути называются кортежами, и мне часто приходится конвертировать эти объекты в кортежи. Это означает, что у меня в основном много функций, подобных этой:
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)
Хотя это «работает», проблема в том, что это совсем не безопасно. Такое ощущение, что должен быть безопасный для этого способ, поскольку вся необходимая информация доступна во время компиляции. Следовательно, мне было любопытно, знает ли кто-нибудь безопасный для типа способ реализации чего-то подобного?