Typescript - 检查一个对象是否具有所有接口属性

10

假设我有一个接口:

export interface MyDocument {
    id: string,
    collection: string[];
}

我随后创建了一个新的变量(将现有变量转换为该类型): const workingDocument = <MyDocument>document; 最后,我使用以下if语句块来检查它是否实际包含了我在接口中指定的所有内容:
if (!workingDocument.id) {
   throw new Error("Document missing `id`");
} else if (!workingDocument.collection) {
   throw new Error("Document missing `collection` array");
}

然而,我似乎不太喜欢这种方法,因为那个if语句可能会越来越长,维护起来也不太好。

有更好的方法吗?

谢谢。


1
我见过的TypeScript最糟糕的滥用是在构造函数中使用this:constructor(data: any) { Object.assign(this,data); } - voila - 无论你传递什么到这个类中,它都会自动遵循类上的任何属性,根据编译器...运行时是另一回事。 TypeScript应该在编译时捕获潜在问题,利用它的优势。如果你在进行类型转换时不确定你正在转换什么,并最终编写if语句进行检查,那么你就使用TypeScript错误了。 - Adam Jenkins
如果在接口中声明的所有属性在其具体类中未定义,则理想情况下,TypeScript编译器会抛出错误以执行相同操作。理想情况下,您不应使用if..else语句检查值是否存在。 - Aamol
2个回答

13

如果我理解正确,您正在要求对一个对象进行运行时检查,以确定它是否包含接口定义的所有属性。仅凭接口本身是不可能实现此目的的,因为与接口关联的类型信息并没有传递到运行时;换句话说,只有在我们运行TypeScript编译器时,接口才有用。

您可以创建包含接口所有属性的模式,然后循环遍历该模式,检查所有属性是否存在于您的对象中。以下是如何实现的例子。我已经将示例封装在用户定义的类型保护中。

export interface MyDocument {
    id: string,
    collection: string[];
}

const isMyDocument = (input: any): input is MyDocument => {

    const schema: Record<keyof MyDocument, string> = {
        id: 'string',
        collection: 'array'
    };

    const missingProperties = Object.keys(schema)
        .filter(key => input[key] === undefined)
        .map(key => key as keyof MyDocument)
        .map(key => new Error(`Document is missing ${key} ${schema[key]}`));

    // throw the errors if you choose

    return missingProperties.length === 0;
}

const obj = {};

if (isMyDocument(obj)) {
  // the compiler now knows that obj has all of its properties
  obj.collection;
} 

以下是上述代码可在TypeScript Playground中查看

评论区问题的回答

以下是使用...运算符扩展模式的方法。

interface ParentDocument { 
    id: string,
    collection: [],
}

interface ChildDocument extends ParentDocument { 
    name: string;
}

const schemaParent: Record<keyof ParentDocument, string> = {
    id: 'string',
    collection: 'array'
};

const schemaChild: Record<keyof ChildDocument, string> = {
    name: 'string',
    ...schemaParent,
};

我认为这是有道理的,但是假设export interface MyDocument还扩展了另一个接口,那么isMyDocument中的schema需要列出所有这些属性吗?有没有可能避免不得不重新列出所有这些属性? - userMod2
@userMod2 我编辑了问题,并提供了使用 { ...obj } 的示例。 - Shaun Luttin
太好了 - 它能运行。但我不知道为什么哈哈!如果子接口中没有name: string;,那么整个接口可以被删除吗? - userMod2
明白了 - 所以如果 interface ParentDocument extends AnotherDocInterface - 我可以假设我仍然可以使用相同的设计? - userMod2
我移除了 const schemaChild,但是现在 ...schemaParent 在哪里被使用了? - userMod2
显示剩余4条评论

1
如果您正在内部创建/使用此文档类型,则可以使用类型/接口来断言其类型-仅供自己使用-而无需进行强制转换。
但是,如果这些文档来自于您的TypeScript应用程序之外,您将需要采取某种形式的手动类型保护/检查(您想要避免的事情)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接