检查对象是否在运行时使用TypeScript实现接口


75

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

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

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

interface DebugConfig {
    logLevel?: number;
}

...

由于可以使用自动填充等功能,因此可以方便地访问各种属性。

问题:有没有一种方法可以使用此声明来检查我加载的文件的正确性?即我没有意外的属性?



Answers:


36

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

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


13
有趣。看起来很容易自动生成。
MasterScrat

1
是的,它可以是自动化的,并且在常见情况下确实很容易。但是,用户定义的防护可以执行某些特殊的操作,例如检查数组的长度或针对正则表达式验证字符串。每个字段的注释会有所帮助,但是我认为现在应该是一类,而不是接口。
Teodor Sandu

11
@RichardForrester我的问题是“有没有一种方法可以使用类型声明来检查对象的正确性”。这个答案不使用类型声明。相反,它需要编写与类型声明完全冗余的测试,而这正是我要避免的。
MasterScrat

23

没有。

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

@JasonEvans指出,来自https://stackoverflow.com/a/16016688/318557

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


1
这不再准确。Typescript编译器标记experimentalDecoratorsemitDecoratorMetadata允许记录类型信息,请参阅我写的关于在运行时使用此信息的库的答案。
Moshe Gottlieb

1
这个答案对于所问的问题是完全正确的,但是Google员工应该注意,有很好的解决方案。考虑一下Alexy关于类验证的建议(我的偏爱),DS关于第三方接口构建器的建议以及teodor关于类型保护的建议。
jtschoonhoven

20

这是另一种替代方法,专门用于此目的:

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()方法来确保没有未知属性。


12

这是个好方法。您可以使用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);
}


这两个软件包都很棒-为我节省了很多工作!结合使用生成的模式和ajv
danday74 '20

7

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

当消耗来自非打字稿代码对象,我使用了JSONSchema验证包,如AJV,用于运行时间验证,和一个.d.ts文件发生器(如DTSgeneratorDTSgenerator)从编译打字原稿类型定义我JSONshcema。

主要警告是,由于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[];
}

4

是。您可以使用我之前发布的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函数是递归运行的,因此您也可以使用它来验证嵌套对象。您可以在此处找到完整的工作示例


1
整洁,希望在TypeScript中内置这样的东西。我认为这是AtScript要做的,而Angular 2则需要。
jpierson

5
官方TypeScript编译器不会(可能永远不会)涵盖反射,因为它表示为“超出范围”。对我来说,这是一个为期10天的开发工作,而且我不在核心团队中:在变得有效之前,我必须学习很多东西。TypeScript团队的一名成员可以在一周或更短的时间内实现这一目标。简而言之:TypeScript中的反射实现没有什么不可能或太难了。
pcan

如何使用此isValid功能?从我看到的结果来看,它不是从您的包裹中导出的。它是从示例中的验证程序文件中导出的,但未包含在软件包中。将其包含在我的代码中的最佳方法是什么?
秒。

1
@S ..该isValid功能只是反射功能的展示;如果您想构建一个成熟的类型检查器,则应该自己完成,或者使用其他类似的项目(但目前它们无法利用反射)。这里的重点不是类型检查,而是编译器提供一种在运行时引用接口的能力。请注意,reflec-ts项目目前处于待命状态,因为我正在等待转换器可扩展性API以便与编译器内核脱钩。
pcan


1

您可以使用类验证

  1. 将接口替换为类。
    猫类{
        @IsNotEmpty()名称:字符串;
    }

    //静态输入是可行的!
    const cat:Cat = { 
        名称:“ Barsik”
    };

  1. 创建验证功能。例:
    从“类验证器”导入{validateSync};

    类型数据= {
        [key:string]:任意;
    };

    //创建新的类实例,将其填充并通过“ class-validator”进行验证
    export const validate = <D扩展数据,C扩展{new():D}>
      (数据:D,classTemplate:C):布尔值=> {
        const instanceClass = new classTemplate();
        Object.keys(data).forEach((key)=> {
            instanceClass [key] = data [key];
        });
        返回!validateSync(instanceClass).length;
    }

  1. 使用类作为接口进行静态输入并使用类进行验证
    如果(validate(cat,Cat)){
      // 好
    }其他{
      //错误
    }


1

我意识到这个问题很旧,但是我只是使用装饰器为此目的编写了自己的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

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


0

我不知道您的配置文件是什么样子,但最明显的是json文件,尽管我将使用json模式来验证文件是否适合该模式。

这是JSON模式v4文档:http : //json-schema.org/documentation.html

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

当然,您必须基于接口编写模式,但是不能直接使用它们。


1
是的,它是JSON,但我的目标是在运行时使用已存在的接口(或任何我不必两次编写配置结构的其他解决方案)对其进行验证
MasterScrat

1
您可以在运行时验证该文件(向其加载ajax请求并检查其是否有效)。但让我解释一件事:接口是您的javascript对象的一种架构,它仅在编译时使用。但是,它缺少太多信息无法用作验证器。例如,您将如何在您的接口中编写某些数组应包含至少三个枚举值的信息?您必须编写模式以具有更大的对象外观灵活性。有一些发电机网上说的构建模式基于像您的JSON文件jsonschema.net
马切伊Kwas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.