DEV Community

Cover image for Typescript typeguard strategy: fancy way to check unknown types
Levy Mateus Macedo
Levy Mateus Macedo

Posted on

Typescript typeguard strategy: fancy way to check unknown types

Introduction

Typescript is an incredible combination with javascript, by the way just javascript is becomes unfeasible because it does not have primitive variable types. But, to life is not just sweet, and the 'unknown' and 'any' values exists to frighten us.

How it works

However, not everything in the life of a TypeScript developer is wonderful. You've probably already made a request to a rest API that has a super big response. Then you needed to validate all this complex data. This is tedious and error-prone...

Well, a good developer is to write things that work well and easy to maintain.

But somethimes we don't need to add a new package to the project.

That's why I bring it in an easy way. Which can help you validate types like a piece of cake.

The idea of ​​this strategy is simple: to facilitate and reduce code smells simply with small functions that check the types of values.

In the end of the day, the use is simple and the result is magical.

We define functions that check a value for a type. In other words, a function for each given type: boolean, string, number, object etc.

See the code below.

// this base functions is a callback called in the nested objects and arrays
export type TypeGuard<T> = (val: unknown) => T

// custom convenient typeguard thats make a way to customize the values
export type CustomTypeGuard<T> = (
  callback?: (val: T) => T
) => TypeGuard<T>

export const string: CustomTypeGuard<string> = 
  (callback) => (val: unknown) => {
    if (typeof val !== 'string') 
      throw new Error("Invalid string value")
    return  callback ? callback(val) : val
}

export const number: CustomTypeGuard<number> = 
  (callback) => (val: unknown) => {
    if (typeof val !== 'number')
      throw new Error("Invalid number value")
    return callback ? callback(val) : val
}

export const boolean: CustomTypeGuard<boolean> = 
  (callback) => (val: unknown) => {
    if (typeof val !== 'boolean') 
      throw new Error("Invalid boolean value")
    return callback ? callback(val) : val
}

export function array<T>(inner: TypeGuard<T>) {
    return (val: unknown): T[] => {
        if (!Array.isArray(val)) 
          throw new Error("Invalid array value");
        return val.map(inner)
    }
}

export function object<
  T extends Record<string, TypeGuard<any>>
>(inner: T) {
  return (val: unknown): { 
    [P in keyof T]: ReturnType<T[P]> 
  } => {
      if (val === null || typeof val !== 'object') 
        throw new Error("Invalid object");

      const out: { [P in keyof T]: ReturnType<T[P]> } = {} as any;

      for (const k in inner) {
          out[k] = inner[k]((val as any)[k])
      }

      return out
  }
}
Enter fullscreen mode Exit fullscreen mode

Take a look to the callback function defined in the CustomTypeGuard. The ideia behind this, is to add a function to handle each value in the final 'schema' object. This is great for manipulate e process some data from a api response for example.

With these functions defined we can use them like code below.

import { string, object, array, number, boolean } from "typeguard"

// the schema receive a function
// this function validates an object using the schema as base
const schema = array(
    object({
        name: string((val) => `${val} doe`),
        id: number((val) => val + 1),
        birthdate: string(
          (val) => new Date(val).toLocaleDateString("pt-br")
        ),
        ready: boolean(),
        parents: array(
            object({
                name: string(),
            })
        ),
    }),
)

// Wich this line below we can get the type of the previous created schema and use this before
type User = ReturnType<typeof schema>

const noTypedUser = [{ 
  name: 'jhon', 
  id: 1, 
  birthdate: new Date().toISOString(), 
  ready: true,
  parents: [{ name: 'jhon' }] 
}]

// if any value is not ok
// an error is thrown
const user: User = schema(noTypedUser);

console.log(user);
Enter fullscreen mode Exit fullscreen mode

Can you see this code running in the ts playground here.

Can you get the code snippet in gist here.

Be Happy πŸ˜„

Conclusion

Today, in the community, there are thousands of extremely good technologies. However, a large package is often not needed to solve a simple problem. All it takes is a simple, well-done implementation and we can achieve what we want quickly.

The typeguard strategy with typescript allows us to define a 'schema' object that can check the validity of the type of a value as well as make any change/validation of complex data through a simple callback without something large and difficult to understand, leaving the code clean and easy to read.

References

Top comments (0)