How to implement object validation using TypeScript?

Viewed 15155

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?

5 Answers

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

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

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.

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

Related