Explanation
Defining all your types and interfaces using TypeScript provides a degree of type safety when using JavaScript, as it's possible to catch potential errors while writing code, instead of having to see them in execution. However, this is a challenge when it comes to communicating with external systems, where we cannot guarantee that the data they send us will align with the definitions we have written.
The solution to this is to write a typeguard function, which checks that an object meets all of our expectations when we expect it to be of a particular type.
However, the task of writing a typeguard is quite tedious. Luckily, it's also quite formulaic, which means it's often possible to generate a typeguard function based off a type definition. That's what this tool, TypeGuardian, is for.
Options
TypeGuardian will remember your preferred options. Here is how they work:
Allow enhanced debugging
When this option is enabled, TypeGuardian will generate a function that uses TypeScript's asserts
keyword, which will throw an error if a tested value doesn't meet your type requirements. This function is then called within your type's generated typeguard function, which returns a boolean value as normal.
By throwing an error, instead of just returning a boolean, it's possible to provide information as to how the typeguard fails.
In order to expose this information, typeguard functions generated with this option enabled will have an optional errorLogger
argument, which will allow you to log the failure message to somewhere like the console. For example, you might call isMyCustomType(value, console.error)
to log a failure message to the console as an error.
Any nested typeguards, for more complex types, will also be passed this errorLogger
function. So it's expected that they will all have been generated with this option enabled.
Indentation
This option allows you to select your preferred type of indentation.
Strategy
The typeguard functions generated here follow a certain format:
1. Type assertion
TypeGuardian starts by asserting that the value being tested is of the correct type, which will allow us to access the properties that we want to check for.
Usually, type assertions in TypeScript can be unsafe and should be avoided where possible. In this case, because we're checking it piece by piece rather than actually trying to use any of it, we shouldn't run into trouble.
2. Object check
Before checking any properties, we check that the value is an object. Because checking typeof null
also results in 'object'
, we also need to check that the value is not null
.
3. Check each property
This is the main part of the typeguard function. Property by property, we check that each one has the correct type.
As soon as we find any property that doesn't meet our expectations, we return false
because the value has failed our type check.
These conditions are each written in an "unless" form, where the innermost part of the condition is a positive assertion (e.g. if a property should be a string, it checks that it is a string), but then that positive part is inverted. So it can be read as "unless this property meets our expectations… return false".
There are several types of check that can be done here. The most basic is checking the result of using the typeof
operator, when checking for primitive types like string
. TypeGuardian can also handle union types and array types.
4. Return true
If the typeguard function passes through all our checks without a single failure, then we can say that the value meets our expectations, and return true
.
Shortcomings
TypeGuardian is a fairly simple and rigid tool, and it relies on type definitions fed into it meeting certain criteria:
- Type definitions should describe object types
- If any properties have complex types, they should have their own type definition with its own separate typeguard function
- TypeGuardian assumes that all custom types and interfaces will use a PascalCase naming convention
- Because TypeGuardian doesn't contain a proper parser, it takes some shortcuts when checking for block comments. It assumes all block comments take the form of JSDoc comments, and any line starting with a
*
is part of a block comment. - TypeGuardian doesn't currently support unions that include arrays of union types, such as
Array<string | number> | null
.