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

Mas*_*rat 49 javascript typescript

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

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

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

interface DebugConfig {
    logLevel?: number;
}

...
Run Code Online (Sandbox Code Playgroud)

这样可以方便地访问各种属性,因为我可以使用自动填充等.

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

Teo*_*ndu 21

"有"方法,但你必须自己实施.它被称为"用户定义的类型保护",它看起来像这样:

interface Test {
    prop: number;
}

function isTest(arg: any): arg is Test {
    return arg && arg.prop && typeof(arg.prop) == 'number';
}
Run Code Online (Sandbox Code Playgroud)

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

现在,在运行时,您将使用isTest()验证对象是否尊重接口.在编译时,typescript会选择守卫并按预期处理后续使用,即:

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
}
Run Code Online (Sandbox Code Playgroud)

这里有更深入的解释:https://basarat.gitbooks.io/typescript/content/docs/types/typeGuard.html

  • 有趣。看起来很容易自动生成。 (5认同)
  • @RichardForrester我的问题是“有没有一种方法可以使用类型声明来检查对象的正确性”。这个答案不使用类型声明。相反,它需要编写与类型声明完全冗余的测试,而这正是我要避免的事情。 (4认同)
  • 一旦定义了接口 Test,您就不需要围绕“什么是测试”有任何灵活性。你刚刚定义了它。它是一个具有名为“prop”的属性的对象,该属性是一个数字。我不必编写更多的 Typeguard 代码来定义测试是什么,对吧? (2认同)

Mas*_*rat 17

没有.

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

来自/sf/answers/1121168191/,正如@JasonEvans所指出的那样

  • 这不再准确。打字稿编译器标志“experimentalDecorators”和“emitDecoratorMetadata”允许记录类型信息,请参阅我编写的在运行时使用此信息的库的答案。 (3认同)

DS.*_*DS. 11

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

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);
Run Code Online (Sandbox Code Playgroud)

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


pca*_*can 6

是.您可以使用我之前发布的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");
Run Code Online (Sandbox Code Playgroud)

这是输出:

isValid(personOk):  true

Field name should be string but it is number
isValid(personNotOk):  false
Run Code Online (Sandbox Code Playgroud)

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

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

Jth*_*rpe 6

我怀疑TypeScript(明智地)遵守Curly定律,而Typescript是一个转换器,而不是对象验证器.也就是说,我也认为typescript接口会导致糟糕的对象验证,因为接口有一个(奇妙的)有限的词汇表,无法验证其他程序员可能用来区分对象的形状,例如数组长度,属性数,图案属性等

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

主要的警告是,因为JSONschemata能够描述无法通过typescript区分的形状(例如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
Run Code Online (Sandbox Code Playgroud)

哪个生成如下.d.ts文件:

[MyTypes.d.ts]

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

interface typeB{
    type: "B";
    foo: string[];
}
Run Code Online (Sandbox Code Playgroud)


DS.*_*DS. 5

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

typescript-json-schema --required --noExtraProps \
  -o YOUR_SCHEMA.json YOUR_CODE.ts YOUR_INTERFACE_NAME
Run Code Online (Sandbox Code Playgroud)

然后在运行时使用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);
}
Run Code Online (Sandbox Code Playgroud)