47-Nodejs Course 2023: Database Models: Casting Data
Tags: node, typescript, mongodb, fastify
Published: 2022-11-07 06:44:39

---
title: 47-Nodejs Course 2023: Database Models: Casting Data
published: true
description: 
series: Nodejs Course 2023
tags: nodejs, typescript, mongodb, fastify
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ie7q0ygbsdku0geiz6hv.png
# Use a ratio of 100:42 for best results.
---

We added a feature previously where we can set the default value, but what about casting data?

## What is Casting Data?

Casting data is when you want to change the type of data that is being stored in the database. For example, if you have a field that is a string, but you want to store it as a number, you can cast it to a `number`.

So we can change or `mutate` the data before it is stored in the database.

That's the whole idea.

## Types of Casting

There are multiple types, as mostly all data types in JavaScript can be listed here, but we'll do a better thing.

We'll add standard casting types, and we'll add custom casting types, also we'll add a cast handler type as well so we can later add custom casting types.

Now let's define it's type in the types file.

```ts
// src/core/database/model/types.ts

/**
 * Custom cast handler
 */
export type CustomCast = (column: string, value: any, model: Model) => any;

/**
 * Cast types
 */
export type CastType =
  | "string"
  | "bool"
  | "boolean"
  | "number"
  | "int"
  | "integer"
  | "float"
  | "date"
  | "object"
  | "array"
  | CustomCast;
  
/**
 * Model Casts
 */
export type Casts = {
  [column: string]: CastType;
};
```

The `CastType` has a pre-defined types such as `string` and `number`, but also has a `CustomCast` type which is a function that takes the column name, the value, and the model, and returns the casted value.

Custom Cast handler will allow us to make custom casting types, for example we can use it to generate hashed password for the user.

But let's start with the standard types first.

```ts
// src/core/database/model/model.ts
import { Document, ModelDocument, Casts } from "./types";

export default abstract class Model extends CrudModel {
  // ...
  /**
   * Cast types
   */
  protected casts: Casts = {};
}
```

We defined a property called `casts` which is an object that has the column name as the key, and the cast type as the value.

Now the question that remains is how do we use it?

## Using Casting

It's pretty obvious, we'll use it in the `save` method directly before the create or update process.

```ts
// src/core/database/model/model.ts

export default abstract class Model extends CrudModel {
  // ...

  /**
   * Cast types
   */
  protected casts: Casts = {};

  /**
   * Perform saving operation either by updating or creating a new record in database
   */
  public async save(mergedData: Document = {}) {
    this.merge(mergedData);

    // check if the data contains the primary id column
    if (this.data._id && !this.isRestored) {
      // perform an update operation
      // check if the data has changed
      // if not changed, then do not do anything
      if (areEqual(this.originalData, this.data)) return;

      this.data.updatedAt = new Date();

      // 👇🏻 cast data
      this.castData();

      await queryBuilder.update(
        this.getCollectionName(),
        {
          _id: this.data._id,
        },
        this.data,
      );
    } else {
      // creating a new document in the database
      const generateNextId =
        this.getStaticProperty("generateNextId").bind(Model);

      // check for default values and merge it with the data
      this.checkDefaultValues();

      // if the column does not exist, then create it
      if (!this.data.id) {
        this.data.id = await generateNextId();
      }

      const now = new Date();

      // if the column does not exist, then create it
      if (!this.data.createdAt) {
        this.data.createdAt = now;
      }

      // if the column does not exist, then create it
      if (!this.data.updatedAt) {
        this.data.updatedAt = now;
      }

      // 👇🏻 cast data
      this.castData();

      this.data = await queryBuilder.create(
        this.getCollectionName(),
        this.data,
      );
    }
  }
```

What we did here, We added a method called `castData` which will cast the data before it is saved in the database.

Now let's define the `castData` method.

```ts
// src/core/database/model/model.ts

export default abstract class Model extends CrudModel {
  // ...

  /**
   * Cast types
   */
  protected casts: Casts = {};

  /**
   * Cast data
   */
  protected castData() {
    // loop through the casts
    for (const column in this.casts) {
      // get the cast type
      const castType = this.casts[column];

      // get the value
      const value = this.data[column];

      // check if the value is undefined
      if (value === undefined) continue;

      // check if the cast type is a function
      if (typeof castType === "function") {
        // cast the value
        this.data[column] = castType(column, value, this);
      } else {
        // cast the value
        this.data[column] = this.castValue(castType, value);
      }
    }
  }

  /**
   * Cast value
   */
  protected castValue(castType: CastType, value: any) {
    switch (castType) {
      case "string":
        return String(value);
      case "bool":
      case "boolean":
        return Boolean(value);
      case "number":
        return Number(value);
      case "int":
      case "integer":
        return parseInt(value);
      case "float":
        return parseFloat(value);
      case "date":
        if (typeof value === "string") {
          // parse the date string
          return new Date(value);
        } else if (typeof value === "number") {
          // parse the date number
          return new Date(value * 1000);
        } else if (value instanceof Date) {
          // return the date
          return value;
        } else {
          // return the current date
          return new Date();
        }
      case "object":
        if (!value) return {};

        if (typeof value === "string") {
          return JSON.parse(value);
        }

        return value;
      case "array":
        if (!value) return [];

        if (typeof value === "string") {
          return JSON.parse(value);
        }

        return value;
      default:
        return value;
    }
  }
}
```

Woooohooo, what is the heck is going on here?

Let's break it down.

First, we loop through the casts, and get the cast type and the value.

Then we check if the value is undefined, if it is, then we continue to the next iteration.

Then we check if the cast type is a function, if it is, then we call the function and pass the column name, the value, and the model itself.

If the cast type is not a function, then we call the `castValue` method and pass the cast type and the value which will perform our standard casting.

Now let's understand the `castValue` method.

Here where is the best time to use the `switch` statement.

We check the cast type, and based on the cast type, we cast the value.

For example, if the cast type is `date`, then we check the value type, if it is a string, then we parse it as a date, if it is a number, then we parse it as a date, if it is a date, then we return it, and if it is none of the above, then we return the current date.

As you can see there are some casts are alias to each other like `bool` and `boolean`, `int` and `integer`, these are just for convenience, in that case we define multiple cases for the same cast type.

Now let's give it a try.

```ts
// src/app/users/models/user.ts
import { Model } from "core/database";
import { Casts, Document } from "core/database/model/types";

export default class User extends Model {
  /**
   * Collection name
   */
  public static collectionName = "users";

  /**
   * {@inheritDoc}
   */
  protected casts: Casts = {
    isActive: "boolean",
    isVerified: "boolean",
    joinDate: "date",
  };
}
```

We defined here three casts, `isActive` will be casted to a boolean, `email` will be casted to a string, and `joinDate` will be casted to a date.

Now let's try to save a new user.

```ts
// src/app/users/routes.ts
import User from './models/user';

const user = await User.create({
  isVerified: "",
  joinDate: "2021-01-01",
  isActive: "1",
});

await user.save();

console.log(user.data); // { isVerified: false, joinDate: 2021-01-01T00:00:00.000Z, isActive: true }
```

And That's it!

Actually, i just remembered one thing, the `boolean` casting we need to enhance it a little bit.

```ts

  /**
   * Cast value
   */
  protected castValue(castType: CastType, value: any) {
    switch (castType) {
      case "string":
        return String(value);
      case "bool":
      case "boolean":
        // if the value is `true` in string, then turn it into boolean true
        if (value === "true") return true;
        // if the value is `false` in string or zero, then turn it into boolean false
        if (value === "false" || value === "0" || value === 0) return false;
        return Boolean(value);
      case "number":
        return Number(value);
      case "int":
      case "integer":
        return parseInt(value);
      case "float":
        return parseFloat(value);
      case "date":
        if (typeof value === "string") {
          // parse the date string
          return new Date(value);
        } else if (typeof value === "number") {
          // parse the date number
          return new Date(value * 1000);
        } else if (value instanceof Date) {
          // return the date
          return value;
        } else {
          // return the current date
          return new Date();
        }
      case "object":
        if (!value) return {};

        if (typeof value === "string") {
          return JSON.parse(value);
        }

        return value;
      case "array":
        if (!value) return [];

        if (typeof value === "string") {
          return JSON.parse(value);
        }

        return value;
      default:
        return value;
    }
  }
```

We added a little check here about the value, if its `true` in string, then we need to cast it as **true** as boolean value, also if the value is `false` in string or zero, then we need to cast it as **false** as boolean value.

## 🎨 Conclusion

In this article, we learned how to cast values in our models, and we learned how to use the `switch` statement in TypeScript in practice.

In our next article, we will learn how to use the custom cast and create password cast.

## 🚀 Project Repository

You can find the latest updates of this project on [Github](https://github.com/hassanzohdy/nodejs-2023)

## 😍 Join our community

Join our community on [Discord](https://discord.gg/pb2vmdfhGf) to get help and support (Node Js 2023 Channel).

## 🎞️ Video Course (Arabic Voice)

If you want to learn this course in video format, you can find it on [Youtube](https://www.youtube.com/playlist?list=PLGO8ntvxgiZMJc7RN2lIq9WmMOlWZGzmz), the course is in Arabic language.

## 💰 Bonus Content 💰

You may have a look at these articles, it will definitely boost your knowledge and productivity.

General Topics

- [Event Driven Architecture: A Practical Guide in Javascript](https://dev.to/hassanzohdy/event-driven-architecture-the-best-paradigm-that-i-love-to-work-with-in-javascript-and-node-js-1gnk)
- [Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript](https://dev.to/hassanzohdy/best-practices-for-case-styles-camel-pascal-snake-and-kebab-case-in-node-and-javascript-55oi)
- [After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
](https://dev.to/hassanzohdy/after-6-years-of-practicing-mongodb-here-are-my-thoughts-on-mongodb-vs-mysql-574b)

Packages & Libraries

- [Collections: Your ultimate Javascript Arrays Manager](https://dev.to/hassanzohdy/collections-your-ultimate-javascript-array-handler-3o15)
- [Supportive Is: an elegant utility to check types of values in JavaScript](https://dev.to/hassanzohdy/supportive-is-an-elegant-utility-to-check-types-of-values-in-javascript-1b3e)
- [Localization: An agnostic i18n package to manage localization in your project](https://dev.to/hassanzohdy/mongez-localization-the-simplest-way-to-translate-your-website-regardless-your-favorite-framework-4gi3)

 React Js Packages

- [useFetcher: easiest way to fetch data in React Js](https://dev.to/hassanzohdy/usefetcher-the-easiest-way-to-fetch-data-in-react-45o9)

Courses (Articles)

- [React Js: Let"s Create File Manager With React Js and Node Js](https://dev.to/hassanzohdy/lets-create-a-file-manager-from-scratch-with-react-and-typescript-chapter-i-a-good-way-to-expand-your-experience-5g4k)