繁体   English   中英

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

[英]Check if an object implements an interface at runtime with TypeScript

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

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

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

interface DebugConfig {
    logLevel?: number;
}

...

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

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

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

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

不。

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

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

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

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

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

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

我怀疑 TypeScript 是(明智地)遵守 Curly 定律,而 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[];
}

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

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

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

我意识到这个问题很老,但我只是为 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

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

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

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

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

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

您可以使用类验证

  1. 用类替换接口。
class Cat {
        @IsNotEmpty() name: string;
    }
    
    // Static typing works!
    const cat: Cat = { 
        name: "Barsik"
    };
  1. 创建验证函数。 例子:
import { validateSync } from "class-validator";
    
    type data = {
        [key: string]: any;
    };
    
    // Create new class instance 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
    }

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM