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

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

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


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


interface DebugConfig {
logLevel?: number;
}


...

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

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

116260 次浏览

I don't know how your configuration file looks like, but most obvious would be json file, though I would go with json schema to validate if file fits the schema or not.

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

其中一个例子,你可以测试它: https://github.com/fge/json-schema-validator

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

No.

Currently, types are used only during development and compile time. The type information is not translated in any way to the compiled JavaScript 代码。

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

自2015年6月以来,在 TypeScript 回购协议 https://github.com/microsoft/TypeScript/issues/3628中有一个关于这个问题的公开问题

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

我怀疑 TypeScript (明智地)遵循 Curly 定律,而且 TypeScript 是一个传输器,而不是一个对象验证器。也就是说,我也认为类型脚本接口会导致糟糕的对象验证,因为接口有一个(奇妙的)有限的词汇表,不能根据其他程序员可能用来区分对象的形状进行验证,如数组长度、属性数量、模式属性等。

在使用来自非类型脚本代码的对象时,我使用 JSONSchema验证包(如 AJV)进行运行时验证,并使用。D.ts 文件生成器(例如 DTS 发生器DTS 发生器)从 JSONshcema 编译 TypeScript 类型定义。

主要的警告是,由于 JSONschemata 能够描述无法通过类型脚本(例如 属性)区分的形状,因此它不是从 JSON 模式到。T.ds,并且您可能需要对生成的。当使用这样的 JSON 模式时,请使用 d.ts 文件。

也就是说,因为其他程序员可能会使用类似数组长度这样的属性来推断对象类型,所以我习惯于区分那些可能会被 TypeScript 编译器使用 enum 混淆的类型,以防止传译器接受使用一种类型来代替另一种类型,比如:

[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[];
}

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

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

是的,有一个自由主义者这样做 https://github.com/gcanti/io-ts

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

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

这里还有另一种选择,特别是对于这种情况:

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

Ts-interface-check 在运行时使用它们来验证对象。

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

你可以使用 类别验证

  1. 用类替换接口。


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

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


  1. Create a validation function. Example:


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. Use class instead of interface for static typing and class for validation


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


我意识到这个问题已经过时了,但是我刚刚为 JSON 对象和类型脚本编写了自己的验证器,为了达到这个目的,我使用了装饰器。
请点击: T-json-object
自从提出这个问题以来,类型脚本已经有了一些进展,现在有了一些实验性的特性,允许记录类型信息以供以后使用。
下面的示例验证 @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

具有许多其他特性,如自定义验证等。

Just made simple site for generating JavaScript validation code out of typescript interfaces. 注意: 仔细阅读限制。

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!');
}