Reputation: 7366
I load a JSON configuration file at runtime, and use an interface to define its expected structure:
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?
Upvotes: 142
Views: 151530
Reputation: 4019
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:
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!');
}
Upvotes: 2
Reputation: 105
You can use class-validation
class Cat { @IsNotEmpty() name: string; } // Static typing works! const cat: Cat = { name: "Barsik" };
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; }
if (validate(cat, Cat)) { // OK } else { // ERROR }
Upvotes: 3
Reputation: 1352
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.
Now at runtime you would use isTest()
to validate if an object respects an interface. At compile time typescript picks up on the guard and treats subsequent usage as expected, i.e.:
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
Upvotes: 60
Reputation: 4003
I realize this question is old, but I just wrote my own validator for JSON objects and typescript, for this exact purpose, using decorators.
Available here: 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.
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.
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.
Upvotes: 3
Reputation: 7366
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.
From https://stackoverflow.com/a/16016688/318557, as pointed out by @JasonEvans
There is an open issue since Jun 2015 about this in the TypeScript repo: https://github.com/microsoft/TypeScript/issues/3628
Upvotes: 30
Reputation: 24110
Here is another alternative, specifically for this:
ts-interface-builder is a tool you run at build time on your TypeScript file (e.g. foo.ts
) to build runtime descriptors (e.g. foo-ti.ts
).
ts-interface-checker uses these to validate objects at runtime. 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);
You can use strictCheck()
method to ensure there are no unknown properties.
Upvotes: 40
Reputation: 24110
Here's a good way. You can convert a TypeScript interface to JSON schema using typescript-json-schema, e.g.
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, e.g.
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);
}
Upvotes: 17
Reputation: 19680
yes, there is a lib that does it https://github.com/gcanti/io-ts
the idea is simple, have simple checks for properties composed into more complex checks for objects
Upvotes: 4
Reputation: 10167
I suspect that TypeScript is (wisely) adhering to Curly's Law, and Typescript is a transpiler, not an object validator. 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.
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.
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.
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:
[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:
[MyTypes.d.ts]
interface typeA{
type: "A";
foo: string[];
}
interface typeB{
type: "B";
foo: string[];
}
Upvotes: 7
Reputation: 921
Yes. You can do this check at runtime by using an enhanced version of the TypeScript compiler that I released a few time ago. 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. You can find the full working example here
Upvotes: 7
Reputation: 6419
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.
Here's json schema v4 documentation: http://json-schema.org/documentation.html
And one of examples how you could test it: 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.
Upvotes: 0