如何在TypeScript中键入具有已知键和未知键的对象

如何在TypeScript中键入具有已知键和未知键的对象,typescript,conditional-types,Typescript,Conditional Types,我正在寻找为以下对象创建TypeScript类型的方法,该对象具有两个已知键和一个具有已知类型的未知键: interface ComboObject { known: boolean field: number [U: string]: string } const comboObject: ComboObject = { known: true field: 123 unknownName: 'value' } 该代码不起作用,因为TypeScript要求所有属性都

我正在寻找为以下对象创建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条件类型有什么帮助吗?

是您要求的。 让我们执行一些类型操作来检测给定类型是否为联合。它的工作方式是使用条件类型的属性将一个并集扩展到各个成分,然后注意到每个成分都比该并集窄。如果这不是真的,那是因为工会只有一个成员(所以它不是工会):

现在我们可以开始讨论您的特定问题。将
BaseObject
定义为可以直接定义的
ComboObject
的一部分:

type BaseObject = { known: boolean, field: number };
在准备错误消息时,让我们定义一个
properComBoobObject
,这样当你陷入困境时,错误会提示你应该做什么:

interface ProperComboObject extends BaseObject {
  '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!': string
}
主菜来了
VerifyComboObject
接受类型
C
,如果它符合所需的
ComboObject
类型,则返回未触及的类型;否则,它将返回错误的
propercombooobject
(它也不符合)

type VerifyComboObject<
  C,
  X extends string = Extract<Exclude<keyof C, keyof BaseObject>, string>
> = C extends BaseObject & Record<X, string>
  ? IsASingleStringLiteral<X, C, ProperComboObject>
  : ProperComboObject;
让我们测试一下:

const okayComboObject = asComboObject({
  known: true,
  field: 123,
  unknownName: 'value'
}); // okay

const wrongExtraKey = asComboObject({
  known: true,
  field: 123,
  unknownName: 3
}); // error, '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!' is missing

const missingExtraKey = asComboObject({
  known: true,
  field: 123
}); // error, '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!' is missing

const tooManyExtraKeys = asComboObject({
  known: true,
  field: 123,
  unknownName: 'value',
  anAdditionalName: 'value'
}); // error, '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!' is missing
第一个可以根据需要编译。最后三个失败的原因不同,与额外属性的数量和类型有关。错误消息有点神秘,但这是我能做的最好的了

您可以在中看到正在运行的代码


同样,我不认为我建议生产代码使用这种方法。我喜欢玩打字系统,但这个感觉特别好,我不想对任何事情负责

希望对你有帮助。祝你好运

不错的一个@jcalz

它给了我一些很好的洞察力,让我达到了我想要的目的。 我喜欢一个具有一些已知属性的BaseObject,并且BaseObject可以拥有它想要的任意多个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
     },
});
type BaseObject={known:boolean,field:number};
类型CoolType=基本对象和记录;
const asComboObject=(x:C&CoolType):C=>x;
const tooManyExtraKeys=asComboObject({
已知:是的,
字段:123,
未知名称:{
已知:错误,
字段:333
},
附加名称:{
已知:是的,
字段:444
},
});
这样我就可以对我已经拥有的结构进行类型检查,而不会做太多更改


ty

TypeScript不适合这样做。我有一种方法可以强制编译器(使用条件类型)将函数参数约束为与预期的
ComboObject
类型匹配的类型(正好是一个带字符串属性的额外键,没有其他属性),但这是一种可怕的方法,您不希望在任何生产代码中使用它。如果您感兴趣,我可以发布它,但我认为您可能想寻求其他更友好的打字脚本选项。@jcalz是的,如果您可以发布或发送它,那将非常好,它可能会激发一些想法,即使它不是完全可行。这太棒了,谢谢!它有助于理解类型系统是如何工作的。最后一个问题,未知密钥是否可能具有接口类型而不是字符串类型?我假设通过将
Record
更改为
Record
,这是可能的,但这似乎允许在接口中使用任意属性。您所说的“任意属性”是什么意思?它不会允许你遗漏属性或者给它们错误的类型,是吗?如果你指的是多余的财产,是的,这并不奇怪。多余的属性检查只在TypeScript中的特定位置发生。它不具有true,强制编译器拒绝多余的属性将需要另一轮检查。有趣的是,您是正确的,它允许附加属性,但正确地检查了给定属性的类型。感谢到GitHub的链接,这也解释了如何获得完全相同的行为。该示例在TypeScript 3.5.1中失败,错误为“Type'keyof C'不可分配给Type'string'”@jcalz在最新版本的TypeScript中没有更简单的方法可以做到这一点吗?我真的只想要这样的东西:
{foo:number;[key:notFoo]:string}
const asComboObject = <C>(x: C & VerifyComboObject<C>): C => x;
const okayComboObject = asComboObject({
  known: true,
  field: 123,
  unknownName: 'value'
}); // okay

const wrongExtraKey = asComboObject({
  known: true,
  field: 123,
  unknownName: 3
}); // error, '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!' is missing

const missingExtraKey = asComboObject({
  known: true,
  field: 123
}); // error, '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!' is missing

const tooManyExtraKeys = asComboObject({
  known: true,
  field: 123,
  unknownName: 'value',
  anAdditionalName: 'value'
}); // error, '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!' is missing
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
     },
});