【问题标题】:How to type an object with one known and one unknown key in Typescript? [duplicate]如何在 Typescript 中使用一个已知键和一个未知键键入对象? [复制]
【发布时间】:2020-06-17 05:12:03
【问题描述】:

试图为一个相当简单但不可预测的对象创建一个类型。一些例子:

{
  "title": "Foo",
  "x": ["A", "B"]
}
{
  "title": "Bar",
  "y": ["C", "D"]
}

所以,我知道对象会有title: string,而且我知道它会有一个其他属性,string[]类型,但是该属性的 name 可以是任何字符串。

有没有办法在 Typescript 中创建一个适用于此的类型? IE。有没有办法输入一个未知名称的 single 属性? ????


我尝试了以下方法:

  • 抱怨title不是string[]类型,还允许有多个key
type SomeType= {
  title: string;
  [key: string]: string[];
};
  • 有效,但不正确,因为key 只能是string[],并且还允许多个key
type SomeType= {
  title: string;
  [key: string]: string | string[];
};

【问题讨论】:

标签: typescript


【解决方案1】:

这是this question 的副本。在投票结束之前,我会在那里调整我的答案。请注意,这是非常脆弱的,您会感觉到自己在与编译器作斗争。我的建议是将您的数据结构更改为更明显,例如interface SomeType {title:string; unknownPropName: string; unknownPropVal: string[]},并在您需要的任何地方转换为您的版本。你会更快乐。

这是一个丑陋的疯狂通用解决方法,它试图检测你有多少额外的键:

// detect if T is a union
type IsAUnion<T, Y = true, N = false, U = T> = U extends any
  ? ([T] extends [U] ? N : Y)
  : never;

// detect if T is a single string literal
type IsASingleStringLiteral<
  T extends string,
  Y = true,
  N = false
  > = string extends T ? N : [T] extends [never] ? N : IsAUnion<T, N, Y>;

type BaseObject = { title: string };

// if C conforms to desired ComboObject, return C.
type VerifyComboObject<
  C,
  X extends string = Extract<Exclude<keyof C, keyof BaseObject>, string>
  > = BaseObject & Record<
    IsASingleStringLiteral<X, X, "!!!ExactlyOneUnknownPropertyRequired!!!">,
    string[]
  >

// only accept parameters of type C that extend VerifyComboObject<C>
const asComboObject = <C>(x: C & VerifyComboObject<C>): C => x;

// testing
const okayComboObject = asComboObject({
  title: "Foo",
  unknownName: ["A", "B"]
}); // okay

const wrongExtraKey = asComboObject({
  title: "Foo",
  unknownName: 3
}); // error, number not assignable to string[]

const missingExtraKey = asComboObject({
  title: "Foo",
}); // error, '!!!ExactlyOneUnknownPropertyRequired!!!' is missing

const tooManyExtraKeys = asComboObject({
  title: "Foo",
  unknownName: ["A", "B"],
  anAdditionalName: ["A", "B"]
}); // error, '!!!ExactlyOneUnknownPropertyRequired!!!' is missing

Playground link

【讨论】:

  • 绝对希望拥有不那么脆弱的数据类型,但它们来自以 JSON 对象形式存储在数据库中的旧保险应用程序,我唯一能确定的是type Application = { [key: string]: Something }Something 理论上可以是任何东西,但我试图将其缩小到至少涵盖我所知道的模式。看着这些答案,我想也许只是坚持使用any,而不是通过某种规范化函数来运行所有内容。 ?
【解决方案2】:

可以使用泛型类型:

type TitleAndArray<K extends string>
    = K extends 'title'
    ? never
    : { title: string } & { [k in K]: string[] };

请注意,这并不像您希望的那样有用。对象类型不禁止其他属性的存在,因此名为x 的属性的存在并不能保证它的值是一个字符串数组。但是,如果您同时获取对象及其属性名称,如下所示,Typescript 将在调用站点检查 K 是否正确:

function test<K extends string>(key: K, obj: TitleAndArray<K>): void {
    // ok
    let arr: string[] = obj[key];
}

也就是说,Typescript 在这里做了一些不合理的推断,所以如果你使用不同的属性,它仍然会认为该属性存在并且类型为string[]。所以,请谨慎使用。

function unsoundTest<K extends string>(obj: TitleAndArray<K>): void {
    let arr: string[] = obj.somethingElse;
}

Playground Link

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-19
    • 2021-03-31
    • 2022-06-23
    • 1970-01-01
    • 2020-01-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多