Как получить тип объединения всех пар «ключ-значение» в объекте в типограмме?

#typescript

Вопрос:

Допустим, у меня есть такой объект, как этот:

 const person = {
  id: 87
  name: 'Some Name',
  address: {
    street: 'Some street',
    country: 'Some country'
  }
}
 

Я хочу получить тип, который представляет собой объединение всех пар ключевых значений. Таким образом, тип должен быть:

 { id: number } | { name: string } | { address: { street: string; country: string; } }
 

Как это можно сделать? Я попробовал это:

 type PersonInfo = {
  id: number;
  name: string;
  address: {
    street: string;
    country: string;
  }
}

type PersonMap<M extends { [index: string]: any }> = {
  [Key in keyof M]: M[Key]
};

type PersonTest1 = PersonMap<PersonInfo>[keyof PersonMap<PersonInfo>];
// Returns "number | string | { street: string; country: string; }"

type PersonTest2 = PersonMap<PersonInfo>;
// Returns { id: number; name: string; address: { street: string; country: string;} }
 

Как я могу получить тип объединения, описанный выше?

Комментарии:

1. почему бы вам просто не определить типы отдельно, а затем объединить их, чтобы создать свой personinfo?

2. @DanielA. Если бы я мог это сделать, мне просто было интересно, есть ли какой-нибудь короткий путь. Приведенный выше пример является всего лишь примером — на самом деле у меня действительно большой объект с множеством пар ключ-значение, и я хотел бы легко получить тип объединения всех этих пар.

3. Это возможно с помощью записей массива. Судя по тому, что я могу сказать, это невозможно с отдельными объектами k/v.

Ответ №1:

Похоже, вам нужна функция типа формы UnionOfSingleKeyObjects<T> , которая превращает тип объекта T в объединение типов объектов с одним ключом, где каждый keyof T вход появляется ровно один раз. В зависимости от ваших вариантов использования вы можете определить функцию такого типа следующим образом:

 type UnionOfSingleKeyObjects<T extends object> = 
  { [K in keyof T]-?: { [P in K]: T[P] } }[keyof T]
 

И убедитесь, что он работает так PersonInfo , как требуется:

 type PersonKVPairs = UnionOfSingleKeyObjects<PersonInfo>
/* type PersonKVPairs = {
    id: number;
} | {
    name: string;
} | {
    address: {
        street: string;
        country: string;
    };
} */
 

Определение UnionOfSingleKeyObjects -это сопоставленный тип , в котором мы перебираем каждый тип ключа K keyof T , вычисляем рассматриваемый объект с одним ключом для каждого ключа, а затем индексируем его, keyof T чтобы получить объединение всех типов объектов с одним ключом.

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

 type UnionOfSingleKeyObjects<T extends object> =
    keyof T extends infer K ? K extends keyof T ?
    { [P in K]: T[P] } : never : never
 

В любом случае работает; Я склонен использовать сопоставленные типы, потому что их немного легче объяснить, чем распределение условных типов.


В обоих этих подходах объект с одним ключом с ключом K записывается как {[P in K]: T[P] } . В качестве альтернативы это может быть записано как Record<K, T[K]> с использованием Record<K, V> типа утилиты или как Pick<T, K> с использованием Pick<T, K> типа утилиты. У этих других версий есть свои плюсы и минусы, и они могут изменить способ отображения типа в IntelliSense quickinfo, а также то, остаются ли необязательные/ readonly ключи необязательными/ readonly в выводе. Если вы заботитесь о сохранении этих модификаторов и не хотите видеть Pick или Record в своей быстрой информации, вы можете написать это примерно {[P in keyof Pick<T, K>]: T[P]} так:

 type UnionOfSingleKeyObjects<T extends object> =
    { [K in keyof T]-?: { [P in keyof Pick<T, K>]: T[P] } }[keyof T]
 

и мы видим, что такие модификаторы сохраняются:

 type Example = UnionOfSingleKeyObjects<{ a?: string, readonly b: number }>
/* type Example = {
    a?: string;
} | {
    readonly b: number;
} */
 

Опять же, в зависимости от ваших вариантов использования, вас могут волновать или не волновать такие вещи.

Ссылка на игровую площадку для кода

Комментарии:

1. Почему [P в K] требуется в типе UnionOfSingleKeyObjects<T расширяет объект> = { [K в ключе T]-?: { [P в K]: T[P] } }[ключ T] ? Т. е. поскольку K — это всего лишь один ключ-почему мы не можем просто сделать { [K]: T[K] }? Я знаю, что это приводит к ошибке, но я не понимаю, почему, поскольку P и K, похоже, одно и то же.

2. Ну, как вы сказали, это выдает ошибку; синтаксис вычисляемого свойства {[k]: YYY} работает только в том случае, если k это значение , а не тип (и, кроме того, тип k должен быть буквальным или уникальным символом). Мы используем сопоставленные типы, а не вычисляемые свойства. Синтаксис для сопоставленных типов- {[P in XXX]: YYY} XXX это тип ключа или объединение типов ключей, и YYY это тип, от которого, возможно, зависит P . Даже если у вас есть только один ключ, вы должны написать [P in K] , чтобы он работал.