简体   繁体   English

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

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

I load a JSON configuration file at runtime, and use an interface to define its expected structure:我在运行时加载一个 JSON 配置文件,并使用一个接口来定义其预期结构:

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

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

interface DebugConfig {
    logLevel?: number;
}

...

This makes it convenient to access the various properties since I can use autocompletions etc.这使得访问各种属性变得方便,因为我可以使用自动完成等。

Question: is there a way to use this declaration to check the correctness of the file I load?问题:有没有办法使用这个声明来检查我加载的文件的正确性? ie that I do not have unexpected properties?即我没有意外的属性?

There "is" a way, but you have to implement it yourself. “有”一种方法,但您必须自己实施。 It's called a "User Defined Type Guard" and it looks like this:它被称为“用户定义的类型保护”,它看起来像这样:

interface Test {
    prop: number;
}

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

Of course, the actual implementation of the isTest function is totally up to you, but the good part is that it's an actual function, which means it's testable.当然, isTest函数的实际实现完全取决于您,但好的部分是它是一个实际函数,这意味着它是可测试的。

Now at runtime you would use isTest() to validate if an object respects an interface.现在在运行时,您将使用isTest()来验证对象是否遵守接口。 At compile time typescript picks up on the guard and treats subsequent usage as expected, ie:在编译时打字稿会采取守卫并按预期处理后续使用,即:

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
}

More in-depth explanations here: https://basarat.gitbook.io/typescript/type-system/typeguard更深入的解释在这里: https : //basarat.gitbook.io/typescript/type-system/typeguard

No.不。

Currently, types are used only during development and compile time.目前,类型仅在开发和编译期间使用。 The type information is not translated in any way to the compiled JavaScript code.类型信息不会以任何方式转换为已编译的 JavaScript 代码。

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

There is an open issue since Jun 2015 about this in the TypeScript repo: https://github.com/microsoft/TypeScript/issues/3628自 2015 年 6 月以来,TypeScript 存储库中有一个关于此的未解决问题: https : //github.com/microsoft/TypeScript/issues/3628

Here is another alternative, specifically for this:这是另一种选择,专门用于此:

ts-interface-builder is a tool you run at build time on your TypeScript file (eg foo.ts ) to build runtime descriptors (eg foo-ti.ts ). ts-interface-builder是一个在构建时在 TypeScript 文件(例如foo.ts )上运行的工具,用于构建运行时描述符(例如foo-ti.ts )。

ts-interface-checker uses these to validate objects at runtime. ts-interface-checker使用这些在运行时验证对象。 Eg例如

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);

You can use strictCheck() method to ensure there are no unknown properties.您可以使用strictCheck()方法来确保没有未知属性。

Here's a good way.这里有一个好方法。 You can convert a TypeScript interface to JSON schema using typescript-json-schema , eg您可以使用typescript-json-schema将 TypeScript 接口转换为 JSON模式,例如

typescript-json-schema --required --noExtraProps \
  -o YOUR_SCHEMA.json YOUR_CODE.ts YOUR_INTERFACE_NAME

Then validate data at runtime using a JSON schema validator such as ajv , eg然后在运行时使用 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);
}

I suspect that TypeScript is (wisely) adhering to Curly's Law, and Typescript is a transpiler, not an object validator.我怀疑 TypeScript 是(明智地)遵守 Curly 定律,而 Typescript 是一个转译器,而不是一个对象验证器。 That said, I also think that typescript interfaces would make for lousy object validation, because interfaces have a (wonderfully) limited vocabulary and can't validate against shapes that other programmers may use to distinguish objects, such as array length, number of properties, pattern properties, etc.也就是说,我还认为 typescript 接口会导致糟糕的对象验证,因为接口的词汇量(非常)有限,并且无法针对其他程序员可能用来区分对象的形状进行验证,例如数组长度、属性数量、模式属性等。

When consuming objects from non-typescript code, I use a JSONSchema validation package, such as AJV , for run-time validation, and a .d.ts file generator (such as DTSgenerator or DTS-generator ) to compile TypeScript type definitions from my JSONshcema.当消耗来自非打字稿代码对象,我使用了JSONSchema验证包,如AJV ,用于运行时间验证,和一个.d.ts文件发生器(如DTSgeneratorDTSgenerator )从编译打字原稿类型定义我JSONshcema。

The major caveat is that because JSONschemata are capable of describing shapes that cannot be distinguished by typescript (such as patternProperties ), it's not a one-to-one translation from JSON schema to .t.ds, and you may have to do some hand editing of generated .d.ts files when using such JSON schemata.主要的警告是,因为 JSONschemata 能够描述无法通过打字稿(例如patternProperties )区分的形状,它不是从 JSON 模式到 .t.ds 的一对一转换,您可能需要做一些手工使用此类 JSON 模式时编辑生成的 .d.ts 文件。

That said, because other programmers may use properties like array length to infer object type, I'm in the habit of distinguishing types that could be confused by the TypeScript compiler using enum's to prevent the transpiler from accepting use of one type in place of the other, like so:也就是说,因为其他程序员可能使用数组长度之类的属性来推断对象类型,所以我习惯于区分可能被 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

Which generates a .d.ts file like so:它会生成一个.d.ts文件,如下所示:

[MyTypes.d.ts]

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

interface typeB{
    type: "B";
    foo: string[];
}

Yes.是的。 You can do this check at runtime by using an enhanced version of the TypeScript compiler that I released a few time ago.您可以使用我前几天发布的增强版 TypeScript 编译器在运行时执行此检查。 You can do something like the following:您可以执行以下操作:

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");

and this is the output:这是输出:

isValid(personOk):  true

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

Please note that the isValid function works recursively , so you can use it to validate nested objects, too.请注意isValid函数递归地工作,因此您也可以使用它来验证嵌套对象。 You can find the full working example here您可以在此处找到完整的工作示例

yes, there is a lib that does it https://github.com/gcanti/io-ts是的,有一个库可以做到这一点https://github.com/gcanti/io-ts

the idea is simple, have simple checks for properties composed into more complex checks for objects这个想法很简单,将简单的属性检查组合成更复杂的对象检查

I realize this question is old, but I just wrote my own validator for JSON objects and typescript, for this exact purpose, using decorators.我意识到这个问题很老,但我只是为 JSON 对象和打字稿编写了自己的验证器,为此目的,使用装饰器。
Available here: ts-json-object .可在此处获得: ts-json-object
Typescript has moved on a bit since this question was asked, and now has experimental features allowing recording of type information for later usage.自从提出这个问题以来,Typescript 已经有了一些进展,现在具有允许记录类型信息以供以后使用的实验性功能。
The following example validates @required and @optional properties, but also validates their type, even though there is no mentioning of the type in the validation notation.下面的示例验证@required@optional属性,但也验证它们的类型,即使在验证符号中没有提到类型。

Example:例子:

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

Has many other features such as custom validations, etc.具有许多其他功能,例如自定义验证等。

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 文件,尽管我会使用 json 模式来验证文件是否符合模式。

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

And one of examples how you could test it: https://github.com/fge/json-schema-validator以及如何测试它的示例之一: https : //github.com/fge/json-schema-validator

Of course you have to write your schema based on interfaces, but you can't use them directly.当然,您必须根据接口编写架构,但不能直接使用它们。

You can use class-validation您可以使用类验证

  1. Replace interface with class.用类替换接口。
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
    }

Just made simple site for generating JavaScript validation code out of typescript interfaces.刚刚制作了一个简单的站点,用于从打字稿接口生成 JavaScript 验证代码。 Note: Read the limitation carefully.注意:请仔细阅读限制。

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

To pile on the "use this lib" answers, here is mine: I've created a package called ts-data-checker which runs TypeScript language service at runtime to check JSON:为了堆积“使用这个库”的答案,这是我的:我创建了一个名为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