получить словарь / ключи объекта как кортеж в машинописи - PullRequest
0 голосов
/ 27 ноября 2018

Я хотел бы получить правильный тип кортежа с правильными литералами типа из объекта в TS 3.1:

interface Person {
  name: string,
  age: number
}

// $ExpectType ['name','age']
type ObjectKeysTuple = ToTuple<keyof Person>

Почему?:

Чтобы получить правильную строкукортеж литералов при использовании Object.keys(dictionary)

Я не смог найти решение для этого, поскольку keyof расширяется до union, который возвращает ('name' | 'age')[], что определенно не то, что мы хотим.

type ObjectKeysTuple<T extends object> = [...Array<keyof T>]
type Test = ObjectKeysTuple<Person>
// no errors ?not good
const test: Test = ['age','name','name','name']

Связанный:

Ответы [ 3 ]

0 голосов
/ 28 февраля 2019

@ jcalz очень впечатляюще после прочтения вашей реализации я решил написать свою собственную версию "N" рекурсивных ключей, которая идет на бесконечную глубину и сохраняет порядок объединения IE "A" |«B» становится [«A», «B»]

// add an element to the end of a tuple
type Push<L extends any[], T> =
  ((r: any, ...x: L) => void) extends ((...x: infer L2) => void) ?
    { [K in keyof L2]-?: K extends keyof L ? L[K] : T } : never

export type Prepend<Tuple extends any[], Addend> = ((_: Addend, ..._1: Tuple) => any) extends ((
    ..._: infer Result
) => any)
    ? Result
    : never;
//
export type Reverse<Tuple extends any[], Prefix extends any[] = []> = {
    0: Prefix;
    1: ((..._: Tuple) => any) extends ((_: infer First, ..._1: infer Next) => any)
        ? Reverse<Next, Prepend<Prefix, First>>
        : never;
}[Tuple extends [any, ...any[]] ? 1 : 0];



// convert a union to an intersection: X | Y | Z ==> X & Y & Z
type UnionToIntersection<U> =
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

// convert a union to an overloaded function X | Y ==> ((x: X)=>void) & ((y:Y)=>void)     
type UnionToOvlds<U> = UnionToIntersection<U extends any ? (f: U) => void : never>;

// returns true if the type is a union otherwise false
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;

// takes last from union
type PopUnion<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? A : never;

// takes random key from object
type PluckFirst<T extends object> = PopUnion<keyof T> extends infer SELF ? SELF extends keyof T ? T[SELF] : never;
type ObjectTuple<T, RES extends any[]> = IsUnion<keyof T> extends true ? {
    [K in keyof T]: ObjectTuple<Record<Exclude<keyof T, K>, never>, Push<RES, K>> extends any[]
        ? ObjectTuple<Record<Exclude<keyof T, K>, never>, Push<RES, K>>
        : PluckFirst<ObjectTuple<Record<Exclude<keyof T, K>, never>, Push<RES, K>>>
} : Push<RES, keyof T>;

/** END IMPLEMENTATION  */



type TupleOf<T extends string> = Reverse<PluckFirst<ObjectTuple<Record<T, never>, []>>>

interface Person {
    firstName: string;
    lastName: string;
    dob: Date;
    hasCats: false;
}
type Test = TupleOf<keyof Person> // ["firstName", "lastName", "dob", "hasCats"]
0 голосов
/ 26 апреля 2019

Из-за обратной связи, которую я получил в первую очередь и обновил до 18 членов профсоюза, одну версию можно упростить до этого, если вы не заботитесь о реверсировании ...

И можете обрабатывать объекты, а не только ключи объектовили любой другой тип по этому вопросу.

Наслаждайтесь.

/* helpers */
type Overwrite<T, S extends any> = { [P in keyof T]: S[P] };
type TupleUnshift<T extends any[], X> = T extends any ? ((x: X, ...t: T) => void) extends (...t: infer R) => void ? R : never : never;
type TuplePush<T extends any[], X> = T extends any ? Overwrite<TupleUnshift<T, any>, T & { [x: string]: X }> : never;
type UnionToIntersection<U> =(U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never
type UnionToOvlds<U> = UnionToIntersection<U extends any ? (f: U) => void : never>;
type PopUnion<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? A : never;
/* end helpers */
/* main work */
type UnionToTupleRecursively<T extends any[], U> = {
    1: T;
    0: PopUnion<U> extends infer SELF ? UnionToTupleRecursively<TuplePush<T, SELF>, Exclude<U, SELF>> : never;
}[[U] extends [never] ? 1 : 0]
/* end main work */

type UnionToTuple<U> = UnionToTupleRecursively<[], U>;

type LongerUnion = { name: "shanon" } | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
    | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18

declare const TestType: UnionToTuple<LongerUnion> // [18, 17, 16, 15, 14....]
0 голосов
/ 27 ноября 2018

Упомянутый вами вариант использования с типом кортежа для Object.keys() сопряжен с опасностью, и я рекомендую не использовать его.

Первая проблема заключается в том, что типы в TypeScript не "точны" .То есть, только потому, что у меня есть значение типа Person, это не означает, что значение содержит only свойства name и age.Представьте себе следующее:

interface Superhero extends Person {
   superpowers: string[]
}
const implausibleMan: Superhero = { 
   name: "Implausible Man",
   age: 35,
   superpowers: ["invincibility", "shape shifting", "knows where your keys are"]
}
declare const randomPerson: Person;
const people: Person[] = [implausibleMan, randomPerson];
Object.keys(people[0]); // what's this?
Object.keys(people[1]); // what's this?

Обратите внимание, что implausibleMan - это Person с дополнительным свойством superpowers, а randomPerson - это Person с тем, кто знает, какие дополнительные свойства.Вы просто не можете сказать, что Object.keys(), действующий на Person, создаст массив с только известными ключами.Это основная причина, по которой такие запросы функций продолжают получать отклонены .

Вторая проблема связана с упорядочением ключей.Даже если вы знали, что имеете дело с точными типами, которые содержат все и только объявленные свойства интерфейса, вы не можете гарантировать , что ключи будут возвращены Object.keys()в том же порядке, что и интерфейс.Например:

const personOne: Person = { name: "Nadia", age: 35 };
const personTwo: Person = { age: 53, name: "Aidan" };
Object.keys(personOne); // what's this?
Object.keys(personTwo); // what's this?

Наиболее разумные движки JS , вероятно, вернут вам свойства в том порядке, в котором они были вставлены, но вы не можете на это рассчитывать.И вы, конечно, не можете рассчитывать на тот факт, что порядок вставки совпадает с порядком свойств интерфейса TypeScript.Так что вы, скорее всего, будете рассматривать ["age", "name"] как объект типа ["name", "age"], что, вероятно, не очень хорошо.


ВСЕ, ЧТО СКАЗАЛ, Я люблю возиться с системой типов, поэтому я решил сделать кодпревратить объединение в кортеж так же, как ответ Мэтта Маккатчена на другой вопрос .Это также чревато опасностями, и я рекомендую против этого.Предостережения ниже.Вот оно:

// add an element to the end of a tuple
type Push<L extends any[], T> =
  ((r: any, ...x: L) => void) extends ((...x: infer L2) => void) ?
  { [K in keyof L2]-?: K extends keyof L ? L[K] : T } : never

// convert a union to an intersection: X | Y | Z ==> X & Y & Z
type UnionToIntersection<U> =
  (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

// convert a union to an overloaded function X | Y ==> ((x: X)=>void) & ((y:Y)=>void)     
type UnionToOvlds<U> = UnionToIntersection<U extends any ? (f: U) => void : never>;

// convert a union to a tuple X | Y => [X, Y]
// a union of too many elements will become an array instead
type UnionToTuple<U> = UTT0<U> extends infer T ? T extends any[] ?
  Exclude<U, T[number]> extends never ? T : U[] : never : never

// each type function below pulls the last element off the union and 
// pushes it onto the list it builds
type UTT0<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT1<Exclude<U, A>>, A> : []
type UTT1<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT2<Exclude<U, A>>, A> : []
type UTT2<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT3<Exclude<U, A>>, A> : []
type UTT3<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT4<Exclude<U, A>>, A> : []
type UTT4<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTT5<Exclude<U, A>>, A> : []
type UTT5<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? Push<UTTX<Exclude<U, A>>, A> : []
type UTTX<U> = []; // bail out

Давайте попробуем:

type Test = UnionToTuple<keyof Person>;  // ["name", "age"]

Похоже, это работает.

Предостережения: вы не можете сделать это программно для объединения произвольного размера.TypeScript не позволяет вам перебирать типы объединений , поэтому любое решение здесь будет выбирать некоторый максимальный размер объединения (скажем, шесть констант) и обрабатывать объединения до этого размера.Мой несколько сложный код выше разработан так, что вы можете расширить этот максимальный размер в основном путем копирования и вставки.

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

Заключительное предупреждение: он не был проверен, поэтомуможет быть полон всевозможных ? интересных подводных камней, даже если вы сохраняете постоянную версию TypeScript.Если вы серьезно относитесь к использованию подобного кода, вам придется подвергнуть его большому количеству испытаний, прежде чем даже подумать об использовании его в рабочем коде.


В заключение, не делайте ничего, что я показал здесь.Хорошо, надеюсь, это поможет.Удачи!

...