runtypejs
v1.2.0
Published
library allowing run time type validation
Maintainers
Readme
runTypeJs
runTypeJs is a library allowing run time type validation
Table of Contents
Need
Problem
Parsing JSON
Javascript is not typed safe at all, you ask for a number, send a boolean and Javascript is happy
Because of that people created TypeScript which helps but only check type at compile time which is fine until you try to decerialize a json
class User {
name: string = "";
age: number = 0;
fromJson(json: string): User {
const data = JSON.parse(json);
this.name = data.name;
this.age = data.age;
return this;
}
isAdult(): boolean {
return this.age >= 18;
}
}
const user = new User().fromJson('{"name": "John", "age": true}');
console.log(typeof user.age); // boolean, not number as described aboveUnknown type
Appart from parsing json you can also have problems with TypeScript not always error when you won't get the type you expect
-const duration: [number|null, number|null] = [params['min'] || null, params['max'] || null];
+const duration: [number|null, number|null] = [Number(params['min']) || null, Number(params['max']) || null];These 2 lines of code are a commit I made recently in a project, I thought undefined would be converted to null with the || operator
but it is not, I kept getting undefined in the array
which caused our code to make a wrong request to the backend and get empty array as response
Using Number transformed my undefined into a NaN which unlike undefined is converted to null using the || operator
Appending properties
JavaScript lets you do add any property that doesn't exist to an object, this can seem like not a big deal but it can also cause some problems
class User {
name: string = "";
age: number = 0;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const user = new User("John Doe", 42);
user.isMarried = true; // no error
console.log(Object.keys(user)); // ["name", "age", "isMarried"] instead of ["name", "age"] like defined in the classType supperposition
In javascript you can use typeof to check the type of a variable, however you probably know that any custom type will be object
typeof new Date(); // "object"
typeof null; // "object"
typeof { name: "John Doe", age: 42 }; // "object"Both these variables are said to be of type object even though it would be reasonable to think that the first one is a Date and the second one is null
Non-numerical numbers
In javascript you have the NaN value which is a number but litterally means "not a number", you also have Infinity which isn't numerical
const infinity = 1 / 0;
const nan = 0 / 0;
console.log(infinity); // Infinity
console.log(nan); // NaN
typeof infinity; // number
typeof nan; // numberConverting types
This blew my mind when I discovered it, but while working on the same project, I wanted to write a test in which I had to mock angular's Router and I got multiple compilation errors from either TypeScript or Angular, in the end I wrote this code
class myRouter {}
function myFunction(router: Router) {}
myFunction(new myRouter() as unknown as Router);Which is me sending a myRouter to a function which only accepts Router even though myRouter doesn't extend Router,
this shouldn't be possible using a language supposedly typed safe, but TypeScript allows it
Solution
In an ideal world TypeScript would modify your code to add some type checking at runtime, however I don't think Microsoft is going to do that soon and I'm too dumb to make a pull request to a big project like TypeScript
Instead I created this library which allows you to easily add type checking at runtime, now you can at any point in your code add a validation to your variable to ensute they are using the right type
Installation
This library is available on npm, you can install it to your project using one of the following commands
npm install runTypeJs
yarn add runTypeJs
pnpm install runTypeJsUsage
There's multiple ways to use this library, you can go from just a function returning the type of the variable (more precise than typeof)
to a full class in which you can define the structure of the class and use it to validate any instance of the class
and recursively validate other classes in it if they were defined.
getType
This is the most basic function, it takes a variable and returns its type.
It is more precise than typeof as it will return the name of the type instead of object for any custom type
It will also distinguish numbers from NaN and Infinity
import { getType } from "runTypeJs";
console.log(getType(new Date())); // Date
console.log(getType({ name: "John Doe", age: 42 })); // Object
console.log(getType(null)); // null
console.log(getType(42)); // number
console.log(getType(NaN)); // NaN
console.log(getType(Infinity)); // InfinitycheckType
This function takes a variable and a type or an array of types and checks if the variable is of the right type
It will throw an error if the type is not correct
import { checkType } from "runTypeJs";
checkType(42, "number"); // no error
checkType(NaN, "number"); // error
checkType(NaN, ["number", "NaN"]); // no errorcheckStructure
This function takes a variable and a structure and checks if the variable is of the right structure
It will throw an error if the structure is not correct
It will also check if the variable is of the right type
import { checkStructure } from "runTypeJs";
checkStructure({ name: "John Doe", age: 42 }, { name: "string", age: "number" }); // no error
checkStructure({ name: "John Doe", age: NaN }, { name: "string", age: "number" }); // error
checkStructure({ name: "John Doe", age: NaN }, { name: "string", age: ["number", "NaN"] }); // no error
checkStructure({ name: "John Doe" }, { name: "string", age: "number" }); // error
checkStructure({ name: "John Doe", age: 42, married: true }, { name: "string", age: "number" }); // errorTypeChecker
This class is allow you to define the structure of a class once and then use it to validate any instance of the class
Defining a structure
There are 2 ways to define a structure, either you write it by hand or you use an instance of the class and let the library do it for you
Be aware that the second method will set the type of the properties to the type used in the instance, it won't know if the property can accept multiple types
import { TypeChecker } from "runTypeJs";
class User {
name: string|[string, string] = "";
age: number = 0;
}
new TypeChecker().define("User", { name: ["string", "Array"], age: "number" }); // define User as { name: [string, Array], age: number }
new TypeChecker().defineFromInstance(new User()); // define User as { name: string, age: number }, not knowing that name can be an arrayChecking a type
You can check a type using the checkType method, unlike the checkType function, this method will check the structure of what you are passing if you defined it
import { TypeChecker } from "runTypeJs";
class User {
name: string|[string, string] = "";
age: number = 0;
constructor(name: string|[string, string], age: number) {
this.name = name;
this.age = age;
}
}
const typeChecker = new TypeChecker();
typeChecker.checkType(new User(["John", "Doe"], 42), "User"); // no error
typeChecker.define("User", { name: "string", age: "number" });
typeChecker.checkType(new User("John Doe", 42), "User"); // no error
typeChecker.checkType(new User(["John", "Doe"], 42), "User"); // error, expected string but got ArrayChecking a structure
You can check a structure using the checkStructure method, unlike the checkStructure function, this method will check the structure of the typed stored in the class you are passing if you defined them
import { TypeChecker } from "runTypeJs";
class User {
name: string|[string, string] = "";
age: number = 0;
constructor(name: string|[string, string], age: number) {
this.name = name;
this.age = age;
}
}
class Dog {
owner: User = new User("", 0);
age: number = 0;
constructor(owner: User, age: number) {
this.owner = owner;
this.age = age;
}
}
const typeChecker = new TypeChecker();
typeChecker.defineFromInstance(new Dog());
typeChecker.checkStructure(new Dog(new User(["John", "Doe"], 42), 3)); // no error
typeChecker.define("User", { name: "string", age: "number" });
typeChecker.checkStructure(new Dog(new User(["John", "Doe"], 42), 3)); // error, expected string but got Array
typeChecker.checkStructure(new Dog(new User("John Doe", 42), 3)); // no errorContributing
New issues
If you find a bug or have a feature request, please open an issue on the repository and I will try to answer it as soon as possible
Pull requests
If you want to contribute to the project, feel free to open a pull request for one of the existing issues, I will review it and merge it if it is good.
If you want to add a new feature, please open an issue first to discuss it with me before opening a pull request
If you do end up opening a pull request, there are some rules that you need to follow:
- Make sure to write tests for your code, I have a pipeline that checks for them and the coverage's threshold is set to 100% so every case must be covered
- Format the code using
pnpm formatso that it has the same style as the rest of the project - Lint the code using
pnpm lintto ensure code quality, you may need in some cases to add an exception to the linter like allowinganyto the getType function but don't use this if you don't have to - Please write your code in a clean and readable way, I will not merge your code until I am satisfied with it
- If needed, you should write documentation for your code in the README.md file so that people using the library can understand how it works
License
This project is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License
