0

Как в TypeScript типизировать объект с известными и неизвестными ключами?

9

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

interface ComboObject {
  known: boolean;
  field: number;
  [U: string]: string;
}

const comboObject: ComboObject = {
  known: true,
  field: 123,
  unknownName: 'value'
}

Этот код не работает, потому что TypeScript требует, чтобы все свойства соответствовали типу, заданному для индексной сигнатуры. Однако я не хочу использовать индексные сигнатуры, я хочу типизировать одно поле, тип которого я знаю, но имя которого не знаю.

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

interface ComboObject {
  [U: string]: boolean | number | string;
}

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

Существует ли лучший подход? Могут ли чем-то помочь условные типы в TypeScript 2.8?

3 ответ(ов)

0

Похоже, что более простой способ сделать это — использовать тип пересечения:

type ComboObject = {
  known: boolean;
  field: number;
} & {
  [key: string]: string | number | boolean;
};

Этот код говорит TypeScript наследовать свойства слева, но при этом не выдает ошибок, когда вы добавляете дополнительные параметры с неизвестными типами.

Существует и более подробное и сложное решение, которое может предоставить дополнительные ограничения, явно исключая известные типы:

type ComboObject = {
  known: boolean;
  field: number;
} & Record<Exclude<string, "known" | "field">, string | number | boolean>;

Пример использования:

const combine: ComboObject = {
  known: true,
  field: 1,
  name: "name",
  age: 1,
  isAlive: true,
};

combine?.known; // boolean
combine?.field; // number
combine?.name; // string | number | boolean
combine?.age; // string | number | boolean
combine?.isAlive; // string | number | boolean

Таким образом, вы получаете тип ComboObject, который включает известные свойства и позволяет добавлять любые другие свойства с типами string, number или boolean.

0

Отличный подход, @jcalz!

Он дал мне полезное понимание для достижения того, что мне нужно. У меня есть базовый объект (BaseObject) с некоторыми известными свойствами, и этот базовый объект может содержать неограниченное количество других базовых объектов.

type BaseObject = { known: boolean, field: number };
type CoolType<C, X extends string | number | symbol = Exclude<keyof C, keyof BaseObject>> = BaseObject & Record<X, BaseObject>;
const asComboObject = <C>(x: C & CoolType<C>): C => x;

const tooManyExtraKeys = asComboObject({
     known: true,
     field: 123,
     unknownName: {
         known: false,
         field: 333
     },
     anAdditionalName: {
         known: true,
         field: 444
     },
});

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

Спасибо!

0

Я столкнулся с серьезной проблемой, но наконец нашел хорошее и простое решение!

export type Overwrite<Base, Overrides> = Omit<Base, keyof Overrides> & Overrides;

export type LocationsType = Overwrite<
  {
    [key: string]: LocationType
  },
  {
    add: (location: LocationData) => void
    addMultiple: (locationsData?: LocationsData) => void
    getAllSources: (maxAmount?: number) => SourceType[]
  }
>

Что это означает? Все ключи - строки с значением LocationType, кроме ключей в Overrides. Эти конкретные строки имеют заданные значения.

Больше не нужно использовать конструкцию { [key: string]: LocationType | () => void | (someParam: string) => void | any } 🎉🎉🎉


Для полноты картины, это всего лишь небольшое изменение, так как я уже использовал Overwrite для этого шаблона:

const customerKeys = ['id', 'firstName', 'lastName', 'birthDate'] as const
type CustomerKey = typeof customerKeys[number]
export type CustomerData = Record<CustomerKey, string>
export type CustomerType = Overwrite<
  CustomerData,
  {
    id: number
    birthDate: Date
    locations: LocationsType
  }
>

Это типизирует JSON-ответ и разобранный результат, который можно использовать в качестве данных и типа.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь