【问题标题】:Check if an object implements an interface at runtime with TypeScript检查对象是否在运行时使用 TypeScript 实现接口
【发布时间】:2016-02-21 09:29:03
【问题描述】:

我在运行时加载一个 JSON 配置文件,并使用一个接口来定义它的预期结构:

interface EngineConfig {
    pathplanner?: PathPlannerConfig;
    debug?: DebugConfig;
    ...
}

interface PathPlannerConfig {
    nbMaxIter?: number;
    nbIterPerChunk?: number;
    heuristic?: string;
}

interface DebugConfig {
    logLevel?: number;
}

...

这使得访问各种属性变得很方便,因为我可以使用自动补全等。

问题:有没有办法使用这个声明来检查我加载的文件的正确性?即我没有意外的属性?

【问题讨论】:

标签: javascript typescript


【解决方案1】:

我不知道您的配置文件长什么样,但最明显的是 json 文件,尽管我会使用 json 架构来验证文件是否符合架构。

这里是 json 架构 v4 文档:http://json-schema.org/documentation.html

以及如何测试它的示例之一:https://github.com/fge/json-schema-validator

当然你必须根据接口来编写你的架构,但你不能直接使用它们。

【讨论】:

  • 是的,它是 JSON,但我的目标是在运行时使用已经存在的接口(或任何其他我不必编写配置结构两次的解决方案)在运行时验证它
  • 您可以在运行时验证该文件(使用 ajax 请求加载它并检查它是否有效)。但是让我解释一件事:接口是 javascript 对象的一种模式,它仅在编译时使用。然而,它缺少太多信息,无法用作验证器。例如,您将如何在界面中编写某个数组应包含至少三个枚举值?您必须编写模式才能对对象的外观有更大的灵活性。网上有一些生成器可以根据您的 json 文件构建架构,例如 jsonschema.net
【解决方案2】:

没有。

目前,类型仅在开发和编译期间使用。 类型信息不会以任何方式转换为已编译的 JavaScript 代码。

来自https://stackoverflow.com/a/16016688/318557,正如@JasonEvans 指出的那样

自 2015 年 6 月以来,TypeScript 存储库中有一个未解决的问题:https://github.com/microsoft/TypeScript/issues/3628

【讨论】:

  • 这不再准确。 typescript 编译器标志 experimentalDecoratorsemitDecoratorMetadata 允许记录类型信息,请参阅我对我编写的在运行时使用此信息的库的回答。
  • 这个答案对于所问的问题是严格正确的,但谷歌人应该注意到有很好的解决方案。考虑一下 Alexy 对类验证的建议(我的偏好)、DS 对 3rd 方界面构建器的建议以及 teodor 对类型保护的建议。
【解决方案3】:

是的。您可以在运行时使用我前几次发布的 TypeScript 编译器的增强版本来执行此检查。您可以执行以下操作:

export interface Person {
    name: string;
    surname: string;
    age: number;
}

let personOk = { name: "John", surname: "Doe", age: 36 };
let personNotOk = { name: 22, age: "x" };

// YES. Now you CAN use an interface as a type reference object.
console.log("isValid(personOk):  " + isValid(personOk, Person) + "\n");
console.log("isValid(personNotOk):  " + isValid(personNotOk, Person) + "\n");

这是输出:

isValid(personOk):  true

Field name should be string but it is number
isValid(personNotOk):  false

请注意,isValid 函数以递归方式工作,因此您也可以使用它来验证嵌套对象。你可以找到完整的工作示例here

【讨论】:

  • 很好,希望在 TypeScript 中内置这样的东西。我认为这是 AtScript 所做的并且 Angular 2 需要的东西。
  • 官方 TypeScript 编译器不涵盖(并且可能永远不会)反射,因为它被声明为“超​​出范围”。对我来说,这是一个 10 天的开发工作,而且我不在核心团队中:我必须学习很多东西才能变得有效。 TypeScript 团队的一名成员可以在一周或更短的时间内实现这一目标。简而言之:TypeScript 中的反射实现没有什么不可能或太难
  • 我该如何使用这个isValid函数?据我所知,它不是从你的包中导出的。它是从示例中的验证器文件中导出的,但不在包中。将它包含在我的代码中的最佳方式是什么?
  • @S..isValid 函数只是对reflect-ts 功能的演示;如果你想构建一个成熟的类型检查器,你应该自己做,或者使用其他类似的项目(但他们目前不能利用反射)。这里的重点不是类型检查,而是编译器提供一种在运行时引用接口的方法的能力。请注意,reflect-ts 项目目前处于待机状态,因为我正在等待转换器可扩展性 API 以便与编译器核心分离。
【解决方案4】:

我怀疑 TypeScript (明智地)遵守 Curly 定律,并且 Typescript 是一个转译器,而不是一个对象验证器。也就是说,我还认为 typescript 接口会导致糟糕的对象验证,因为接口的词汇量(非常)有限,并且无法针对其他程序员可能用来区分对象的形状进行验证,例如数组长度、属性数量、模式属性等。

当使用非打字稿代码中的对象时,我使用JSONSchema 验证包,例如AJV,用于运行时验证,以及一个.d.ts 文件生成器(例如DTSgenerator 或@987654324 @) 从我的 JSONshcema 编译 TypeScript 类型定义。

主要需要注意的是,由于 JSONschemata 能够描述打字稿无法区分的形状(例如 patternProperties),因此它不是从 JSON 模式到 .t.ds 的一对一转换,您可以使用此类 JSON 模式时,必须手动编辑生成的 .d.ts 文件。

也就是说,因为其他程序员可能会使用数组长度等属性来推断对象类型,所以我习惯于区分可能被 TypeScript 编译器混淆的类型,使用枚举来防止转译器接受在其他的地方,像这样:

[MyTypes.yaml]

definitions: 
    type-A: 
        type: object
        properties:
            type:
                enum:
                - A
            foo: 
                type: array
                item: string
                maxLength: 2
    type-B: 
        type: object
        properties:
            type:
                enum:
                - B
            foo: 
                type: array
                item: string
                minLength: 3
        items: number

它会生成一个像这样的.d.ts 文件:

[MyTypes.d.ts]

interface typeA{
    type: "A";
    foo: string[];
}

interface typeB{
    type: "B";
    foo: string[];
}

【讨论】:

    【解决方案5】:

    “有”一种方法,但您必须自己实现它。它被称为“用户定义的类型保护”,它看起来像这样:

    interface Test {
        prop: number;
    }
    
    function isTest(arg: any): arg is Test {
        return arg && arg.prop && typeof(arg.prop) == 'number';
    }
    

    当然,isTest 函数的实际实现完全取决于您,但好的部分是它是一个实际函数,这意味着它是可测试的。

    现在在运行时,您将使用isTest() 来验证对象是否尊重接口。在编译时打字稿拿起警卫并按预期处理后续使用,即:

    let a:any = { prop: 5 };
    
    a.x; //ok because here a is of type any
    
    if (isTest(a)) {
        a.x; //error because here a is of type Test
    }
    

    更深入的解释在这里:https://basarat.gitbook.io/typescript/type-system/typeguard

    【讨论】:

    • 有趣。看起来很容易自动生成。
    • 是的,它可以自动化,而且对于常见情况确实很容易。然而,用户定义的守卫可以做一些特殊的事情,比如检查数组的长度或根据正则表达式验证字符串。每个字段的注释会有所帮助,但我认为它现在应该是一个类而不是一个接口。
    • @RichardForrester 我的问题是“有没有办法使用类型声明来检查对象的正确性”。这个答案不使用类型声明。相反,它需要编写与类型声明完全冗余的测试,这正是我想要避免的。
    • 同意!这个答案相当于编写自己的验证函数,独立于接口定义。那有什么意义呢?
    • @Phil 它不等价,这正是大声笑:) 关键是您在定义这样的对象时具有更大的灵活性。它还可以指示代码完成工具了解对象的类型,因此您可能会在某些情况下获得代码完成,否则您不会。如果您需要其中任何一个,那么这可能是一个可以接受的折衷方案,因为唯一的实际答案是“对不起,不能做”。
    【解决方案6】:

    是的,有一个库可以做到这一点https://github.com/gcanti/io-ts

    这个想法很简单,将简单的属性检查组合成更复杂的对象检查

    【讨论】:

      【解决方案7】:

      这是一个好方法。您可以使用 typescript-json-schema 将 TypeScript 接口转换为 JSON 模式,例如

      typescript-json-schema --required --noExtraProps \
        -o YOUR_SCHEMA.json YOUR_CODE.ts YOUR_INTERFACE_NAME
      

      然后在运行时使用 JSON 模式验证器(例如 ajv)验证数据,例如

      const fs = require('fs');
      const Ajv = require('ajv');
      
      // Load schema
      const schema = JSON.parse(fs.readFileSync('YOUR_SCHEMA.json', {encoding:"utf8"}));
      const ajv = new Ajv();
      ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
      var validator = ajv.compile(schema);
      
      if (!validator({"hello": "world"})) {
        console.log(validator.errors);
      }
      

      【讨论】:

      【解决方案8】:

      这是另一种选择,专门用于此:

      ts-interface-builder 是您在构建时对 TypeScript 文件(例如 foo.ts)运行以构建运行时描述符(例如 foo-ti.ts)的工具。

      ts-interface-checker 在运行时使用这些来验证对象。例如

      import {createCheckers} from 'ts-interface-checker';
      import fooDesc from 'foo-ti.ts';
      const checkers = createCheckers(fooDesc);
      
      checkers.EngineConfig.check(someObject);   // Succeeds or throws an informative error
      checkers.PathPlannerConfig.check(someObject);
      

      您可以使用strictCheck() 方法来确保没有未知属性。

      【讨论】:

        【解决方案9】:

        您可以使用class-validation

        1. 用类替换接口。
        类猫{ @IsNotEmpty() 名称:字符串; } // 静态类型有效! 常量猫:猫= { 名称:“巴尔西克” };
        1. 创建一个验证函数。示例:
        从“类验证器”导入 { validateSync }; 类型数据 = { [键:字符串]:任何; }; // 创建新的类实例并通过“class-validator”进行验证 export const validate = (数据:D,类模板:C):布尔=> { const instanceClass = new classTemplate(); Object.keys(data).forEach((key) => { 实例类[键] = 数据[键]; }); 返回 !validateSync(instanceClass).length; }
        1. 使用类代替接口进行静态类型化和使用类进行验证
        如果(验证(猫,猫)){ // 好的 } 别的 { // 错误 }

        【讨论】:

          【解决方案10】:

          我意识到这个问题已经过时了,但我只是为此使用装饰器为 JSON 对象和打字稿编写了自己的验证器。
          可在此处获取:ts-json-object
          自从提出这个问题以来,Typescript 已经有了一些进展,现在具有实验性功能,允许记录类型信息以供以后使用。
          以下示例验证 @required@optional 属性,但也验证它们的类型,即使验证符号中没有提及类型。

          例子:

          import {JSONObject,required,optional,lt,gte} from 'ts-json-object'
          
          class Person extends JSONObject {
              @required // required
              name: string
              @optional // optional!
              @lt(150) // less than 150
              @gte(0) // Greater or equal to 0
              age?: number
          }
          
          let person = new Person({
           name: 'Joe'
          }) // Ok
          let person = new Person({
          }) // Will throw a TypeError, because name is required
          let person = new Person({
           name: 123
          }) // Will throw a TypeError, because name must be a string
          

          具有许多其他功能,例如自定义验证等。

          【讨论】:

            【解决方案11】:

            刚刚创建了一个简单的网站,用于从 typescript 接口生成 JavaScript 验证代码。 注意:仔细阅读限制。

            https://ts-interface-validator.vercel.app/

            【讨论】:

              【解决方案12】:

              要堆积“使用这个库”的答案,这是我的:我创建了一个名为 ts-data-checker 的包,它在运行时运行 TypeScript 语言服务来检查 JSON:

              import { checker } from "ts-data-checker";
              
              export interface PathPlannerConfig {
                  nbMaxIter?: number;
                  nbIterPerChunk?: number;
                  heuristic?: string;
              }
              
              const { checkJson } = checker("PathPlannerConfig", "./nameofthisfile");
              
              if (checkJson(`{ "nbMaxIter": 1 }`)) {
                  console.log('valid!');
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2013-04-07
                • 2016-09-29
                • 2012-03-06
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多