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

时间:2015-11-19 09:56:08

标签: javascript typescript

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

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

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

interface DebugConfig {
    logLevel?: number;
}

...

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

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

12 个答案:

答案 0 :(得分:13)

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

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.gitbooks.io/typescript/content/docs/types/typeGuard.html

答案 1 :(得分:10)

  

没有

     

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

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

答案 2 :(得分:8)

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

isValid(personOk):  true

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

这是输出:

isValid

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

答案 3 :(得分:6)

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

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

主要的警告是,因为JSONschemata能够描述无法通过typescript区分的形状(例如DTS-generator),所以它不是从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 :(得分:6)

这是另一种选择,特别是对此:

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

ts-interface-checker使用它们在运行时验证对象。 E.g。

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

答案 5 :(得分:5)

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

答案 6 :(得分:1)

我不知道你的配置文件是怎么样的,但最明显的是json文件,不过我会使用json架构来验证文件是否适合架构。

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

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

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

答案 7 :(得分:1)

是的,有一个lib可以做https://github.com/gcanti/io-ts

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

答案 8 :(得分: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

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

答案 9 :(得分:0)

您可以使用class-validation

  1. 将接口替换为类。

    class Cat {
        @IsNotEmpty() name: string;
    }

    // Static typing is work !!!
    const cat: Cat = { 
        name: "Barsik"
    };

  1. 创建验证功能。示例:

    import { validateSync } from "class-validator";

    type data = {
        [key: string]: any;
    };

    // Create new class instance, fill it and validate via "class-validator"
    export const validate = <D extends data, C extends {new(): D}>
      (data: D, classTemplate: C): boolean => {
        const instanceClass = new classTemplate();
        Object.keys(data).forEach((key) => {
            instanceClass[key] = data[key];
        });
        return !validateSync(instanceClass).length;
    }

  1. 使用类作为静态输入的接口,并使用类进行验证

    if (validate(cat, Cat)) {
      // OK
    } else {
      // ERROR
    }

答案 10 :(得分:0)

刚刚制作了一个简单的站点,用于从打字稿接口生成 JavaScript 验证代码。 注意:请仔细阅读限制。

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

答案 11 :(得分:0)

为了补充“使用这个库”的答案,这是我的:我创建了一个名为 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!');
}