11

I have an Express API server app and a React client app both implemented in TypeScript. I defined my data models using TypeScript interfaces, and I'm using those interfaces on both ends of the system. However, TypeScript interfaces are compile-time feature only, and I need also runtime type checking, e.g. validating that HTTP POST data (json) conforms to the defined data structure.

So my question is, how could/should I implement runtime object validation utilizing the features provided by TypeScript?

Kitanotori
  • 1,741
  • 5
  • 16
  • 23
  • 1
    You can use a npm module like [`revalidator`](https://www.npmjs.com/package/revalidator) to validate your data – Soubhik Mondal Jun 01 '17 at 02:14
  • 2
    Possible duplicate of [Check if an object implements an interface at runtime with TypeScript](https://stackoverflow.com/questions/33800497/check-if-an-object-implements-an-interface-at-runtime-with-typescript) – 4castle Jun 01 '17 at 02:19
  • @4castle No it is not duplicate. I asked whether TypeScripts interfaces can be utilized also in runtime (I know that by default they can't by themselves), and if not, how to utilize (other) features of TypeScript for implementing validation. There are billions of people saying that TypeScript interfaces are compile-time only, which I already know as written in the question. – Kitanotori Jun 01 '17 at 03:08
  • 1
    @BlazeSahlzen How can I utilize that without having to define my models twice: once for compile-time (TS interfaces) and once for runtime (JSON schema)? – Kitanotori Jun 01 '17 at 03:11
  • 1
    TypeScript has no other features for implementing validation. It is strictly a JavaScript transpiler and nothing more. The duplicate question explains this and why. – 4castle Jun 01 '17 at 03:12
  • It is not be possible to use the typescript interface with `revalidator`, as it has it's own schema structure which is completely different. (unless you were to write a plugin to convert the typescript interfaces to revalidator schema) – Soubhik Mondal Jun 01 '17 at 03:18
  • I have actually the data structure defined also in OpenAPI 2.0 spec (compatible with JSON Schema). So I guess the process would be like this: validate the HTTP payload using JSON Schema model definition, attach the appropriate TypeScript interface to the payload object after it has been successfully validated. I guess it is impossible then to avoid having two sets of definitions. – Kitanotori Jun 01 '17 at 03:38

5 Answers5

2

I've created a super lightweight library called Smoke Screen which does exactly that. It leverages typescript features to perform any kind of object validation within javascript runtime. It's not 100% seamless due to the fact that javascript does not hold any type information at runtime, but thanks to TypeScript decorators, this may be easily done:

class Person {

    @exposed({type: Number})
    age: number;

}

// serialize a Person object into a JSON string
const person = new Person();
person.age = 56.8;
const smokeScreen = new SmokeScreen();
smokeScreen.toJSON(person); // -> '{"age":56.8}'

// deserialize a JSON string into a Person object
let json = JSON.stringify({age: 19});
const person2 = smokeScreen.fromJSON(json, Person);
console.log(person2); // -> Person { age: 19 }

// typing validation
json = JSON.stringify({age: "oops"});
smokeScreen.fromJSON(json, Person); // Error: illegal input - property 'age' must be a number

Additional custom validators may be set, optional parameters and null checking are also supported and enforced. I suggest reading more about it and trying it out.

Aviv Carmi
  • 907
  • 8
  • 21
2

This question is old, but I'd like to share my validation library also.

It's type-script friendly, tiny (no tons of unnecessary functionality) and easily extensible by custom validators.

npm: https://www.npmjs.com/package/checkeasy

github: https://github.com/smbwain/checkeasy

import {alternatives, arrayOf, int, object, oneOf, optional, string} from 'checkeasy';

const myValidator = object({
   a: int({max: 5}),
   b: string(),
   c: optional(float()),
   d: oneOf(['a', 'b', 7] as const),
   e: alternatives([string(), int()]),
   f: arrayOf(string()),
   g: object({
       subP: string(),
   }),
});

const value = myValidator(anyUnsafeData, 'name');
// type of value is: {
//    a: number,
//    b: string,
//    c: number | undefined,
//    d: "a" | "b" | 7,
//    e: string | number,
//    f: string[],
//    g: {subP: string},
//}

It also throws clear human readable messages in errors. E.g.

myValidator({a: 'hello'}, 'data');
// throws: [data.a] should be an integer

myValidator({a: 1, b: 'string', d: 'a', e: true}, 'data');
// throws: All alternatives failed for [data.e]:
//      [data.e.@alternative(0)] should be a string
//      [data.e.@alternative(1)] should be an integer
Roman D
  • 29
  • 2
1

You can try out a framework/library called ts.validator.fluent. Generic object validation. Fluent rules.

https://github.com/VeritasSoftware/ts.validator

NPM Package:

https://www.npmjs.com/package/ts.validator.fluent

Here is an example of how your TypeScript models can be validated using the framework:

/* Install npm package ts.validator.fluent and then import like below */
import { IValidator, Validator, ValidationResult } from 'ts.validator.fluent/dist';

/*TypeScript model*/
class Person {
   Name: string;
}

/* Validation rules */
var validatePersonRules = (validator: IValidator<Person>) : ValidationResult => {
  return validator
             .NotEmpty(m => m.Name, "Name cannot be empty")
        .ToResult();
};

/* Populate model */
var person = new Person();
person.Name = "Shane";

/* Validate model */
/* Sync */
var validationResult = new Validator(person).Validate(validatePersonRules); 
/* Async */
var validationResult = await new Validator(person).ValidateAsync(validatePersonRules);
Shane
  • 99
  • 1
  • 4
1

I also didn't find any good solution for TypeScrpit which is:

  1. Simple to use
  2. Reusable with my existing code
  3. Share the same interfaces and logic as TypeScript

So I created a library called computed-types:

const UserSchema = Schema({
  name: string,
  amount: number,
  flags: array.of(string).optional();
});

type User = Type<typeof UserSchema>;

Equivalent code in Joi:

const UserSchema = Joi.object({
  name: Joi.string().required(),
  amount: Joi.number().required(),
  flags: Joi.array().items(Joi.string()),
});

type User = {
  name: string;
  amount: number;
  flags?: string[];
}

See more details and examples here: https://github.com/neuledge/computed-types

Moshe Simantov
  • 3,937
  • 2
  • 25
  • 35
0

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

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.

Moshe Gottlieb
  • 3,963
  • 25
  • 41