Elegant Typescript Data Validation With Decorators

Patrick Assoa Adou
Level Up Coding
Published in
4 min readFeb 6, 2020

--

My Aim

I want to specify validation rules for the objects I am using in my typescript apps in the most ergonomic, hence elegant, manner possible.

github: https://github.com/kanian/ts-decorator-validation,

npm: https://www.npmjs.com/package/ts-decorator-validation

Tools

I will use Joi and JavaScript decorators. Here’s a small UML diagram to summarize the approach:

A Class, its constructor, the constructor parameters, the class properties and their schemas

About Joi

Joi is a schema description language and data validator for JavaScript. Say you want to validate that a person object such that each person object has a name and age properties. The name must be a non-empty string of minimum 3 characters, while the age is a number that cannot be less than 1. Here’s what you would do:

Then you would validate like this:

Typescript Decorators

Available as an experimental feature of Typescript, decorators provide a way to annotate class and class members declarations. They also provide a meta-programming syntax for the said class and their members. Being experimental, you must enable decorators and metadata emission in your tsconfig.json:

{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}

Moreover, you will have to install the metadata capability, a polyfill for the ECMAScript proposal:

npm i reflect-metadata

In general

A decorator is a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter. Decorators use the form @expression, where expression must evaluate to a function that will be called at runtime with information about the decorated declaration.

I am particularly interested in class, parameter and property decorators.

Class decorators are applied to a class constructor and let you modify a class declaration so that you can observe it or apply any kind of custom behavior. In my case, I want to make sure that a class instance adheres to a given Joi schema. Hence, I will use a decorator factory that takes a given schema and uses it, in the decorator’s expression definition to validate a created instance of the class of interest:

Class schema decorator

For the remaining of this article we will assume this:

Person-related schemas

Now, we can validate an instance at creation as in:

Use schema decorator to validate a Person object

As you can see, besides the creation of the schemas, we only had to add one line of code, the decorator, to our class in order to enforce validation. This is ergonomic, succinctly shows the intent and is elegant.

Yet, we have to create an entire instance before bailing out even when the information showing us that an argument is invalid is available from the argument list. A better approach would be to validate arguments individually, then to create an instance and validate it at the class level. At any rate, an instance won’t be created if a parameter is invalid. To do that, I will use parameter decorators and reflect-metadata. A parameter decorator

[…] will be called as a function at runtime, with the following three arguments: 1.Either the constructor function of the class for a static member, or the prototype of the class for an instance member. 2.The name of the member. 3.The ordinal index of the parameter in the function’s parameter list. (https://www.typescriptlang.org/docs/handbook/decorators.html#parameter-decorators)

This is the code for the parameter decorator:

Parameter decorator

Moreover, I will modify the previous class schema decorator to check for parameter decorators and thus validate the constructor parameters:

Class schema decorator with constructor schema validation

The validateConstructorParams function retrieves existing constructor parameters metadata and validates the given argument against their respective parameter schema:

ValidateConstructorParams: validates the given arguments against their respective parameter schema

We can now bail out as soon as a parameter is invalid:

paramSchema decorator validation example

This is still quite terse and elegant. We can make the code even terser by giving shorter aliases to our decorator during imports.

Now, I am sure that object creation will not complete if the given data is invalid.

I want now to ensure that no modifications of the object’s state should leave it in an invalid state. To achieve this, I will use property decorators; they are called

[…] at runtime, with the following two arguments: 1.Either the constructor function of the class for a static member, or the prototype of the class for an instance member. 2.The name of the member. (https://www.typescriptlang.org/docs/handbook/decorators.html#property-decorators)

The property decorator will build a getter and a setter for the property it annotates. The setter will validate it’s argument against the property decorator’s schema:

propertySchema Decorator
propertySchema decorator validation example

Putting everything together will give us:

All decorators used

Conclusion

Typescript class, parameter and property decorators, as well as reflect-metadata are perfect tools to help validate TypeScript objects in the most ergonomic, hence elegant, manner possible. This declarative approach is terse and conveys intent in a very clear manner.

The code for this article is found at https://github.com/kanian/ts-decorator-validation, while the npm package is https://www.npmjs.com/package/ts-decorator-validation.

--

--