npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

class-transform

v0.7.0

Published

Transformation between plain JavaScript objects and class instances

Downloads

3,884

Readme

class-transform

Continuous Integration NPM Version Minified Size

Class syntax was introduced to JavaScript in ES6. Nowadays you are working with typed instances more than ever. class-transform allows you to transform JSON or plain object into strongly typed instance of a class and vice versa. This tool is very helpful for both the frontend and backend.

// Plain - no type information
{
  id: 1,
  name: 'foo',
  photos: [
    { id: 9, filename: 'cool_whale.jpg', depth: 1245 },
    { id: 10, filename: 'hot_bird.jpg', depth: 6123 }
  ],
  pages: { table: 6, contents: 72 }
}
// Instance - typed by a class
Album {
  id: 1,
  name: 'foo',
  photos: [
    Photo { id: 9, filename: 'cool_whale.jpg', depth: 1245 },
    Photo { id: 10, filename: 'hot_bird.jpg', depth: 6123 }
  ],
  pages: Pages { table: 6, contents: 72 }
}

Started as a fork of class-transformer, this library aims to simplify the API, modernize code, and enhance type safety. Both JavaScript and TypeScript are fully supported.

Table of contents

About this library

In JavaScript, objects can be classified into two categories:

  • Plain objects: Objects that are instances of Object class. Sometimes they are called literal objects, when created via {} notation.
  • Instance objects: Instances of classes with own defined constructor, properties and methods. Usually you define them via class notation.

So, what is the problem?

Sometimes you want to transform plain JavaScript object to an instance of the ES6 class. Once you've parsed some data from a JSON API or a JSON file with JSON.parse, you have plain JavaScript objects, not instances of a class.

For example you have a list of users in your users.json that you are loading:

[
  {
    "id": 1,
    "firstName": "Johny",
    "lastName": "Cage",
    "age": 27
  },
  {
    "id": 2,
    "firstName": "Ismoil",
    "lastName": "Somoni",
    "age": 50
  },
  {
    "id": 3,
    "firstName": "Luke",
    "lastName": "Dacascos",
    "age": 12
  }
]

To load the JSON data, you would write the following code. However, it consists solely of plain objects and lacks type safety.

let response = await fetch("users.json");
let plains = await response.json();
// `plains` variable is just an array of plain objects.
// Type checkers cannot help you with `any` type like this.

To achieve type-safe programming, you can use class-transform. Purpose of this library is to help you to convert your plain objects to the instances of classes you have.

import { Exposed, plainsToInstances } from "class-transform";

class User {
  id = Exposed.number();
  firstName = Exposed.string();
  lastName = Exposed.string();
  age = Exposed.number();
}

let response = await fetch("users.json");
let instances = plainsToInstances(await response.json(), User, []);
// Now each value in `instances` array is an instance of `User`.
// By converting plain objects into class instances,
// type checking becomes available.
// You can use proper class methods as well.

Even inside TypeScript codebases, using classes for JSON can be advantageous over interface and type statements because they are preserved after compilation, enabling true object-oriented programming for reliable runtime behaviors.

Samples

Take a look at the sample code for more examples of usages.

Functions

| Function | Summary | | ------------------- | ----------------------------- | | plainToInstance | Object to T | | plainsToInstances | Array<Object> to Array<T> | | instanceToPlain | T to Object | | instancesToPlains | Array<T> to Array<Object> |

Methods for exposing fields

All field methods provide proper type hint to TypeScript type checker.

| Field method | Type hint | Initial | | ------------------ | ----------------- | ------- | | Exposed.number | number \| null | null | | | number | given | | Exposed.numbers | Array<number> | [] | | Exposed.boolean | boolean \| null | null | | | boolean | given | | Exposed.booleans | Array<boolean> | [] | | Exposed.string | string \| null | null | | | string | given | | Exposed.strings | Array<string> | [] | | Exposed.struct | T | T {} | | Exposed.structs | Array<T> | [] |

There are also methods for specifying options.

| Option method | Role | | ------------------------ | ------------------------------ | | Exposed.alias | Property name in plain objects | | Exposed.toInstanceOnly | Include it only to instance | | Exposed.toPlainOnly | Include it only to plain |

You can combine the effects of these methods by chaining them. Please note that the type method should come at the end of the chain.

class SomeType {
  myField = Exposed.toPlainOnly().alias("my_field").number(36);
}

Strong type safety

Strong type safety is always guaranteed. A class instance will always have the exact set of values that match its fields, with the exact types.

class-transform only transforms class fields that are set as Exposed with plain objects. Fields that are not Exposed will be ignored. This applies to both plainToInstance and instanceToPlain.

import { Exposed, plainToInstance, instanceToPlain } from "class-transform";

class User {
  id = Exposed.number(); // number | null
  firstName = Exposed.string(); // string | null
  lastName = Exposed.string("Johnson"); // string
  favorites = Exposed.strings(); // Array<string>
  isKind = true; // boolean
}

let plain = {
  unkownProp: "Hello there",
  firstName: "Umed",
  lastName: "Khudo",
};

// An instance always guarantees the exact shape and types.
let instance = plainToInstance(plain, User, []);
console.log(instance);
// User {
//   id: null,
//   firstName: 'Umed',
//   lastName: 'Khudo',
//   favorites: [],
//   isKind: true
// }

// Transformation is done exactly on `Exposed` fields.
let plainNew = instanceToPlain(instance);
console.log(plainNew);
// {
//   id: null,
//   firstName: 'Umed',
//   lastName: 'Khudo'
//   favorites: [],
// }

If a property is missing, class-transform will fill it with the initial value, blank array, or an empty child instance depending on the field type. If the field is not Exposed, the value will not be included in the transformation at all.

Each type method has a return type that represents the data, allowing TypeScript's type checker to do its job. It works well with "strict": true of tsconfig.json. If you're using JavaScript, you can set tsconfig.json's compilerOptions.checkJs to true to utilize TypeScript's type checker.

Working with nested structures

When you are trying to transform objects that have nested objects, you need to explicitly specify the type of field by passing the class itself into Exposed.struct or Exposed.structs. An array with the class parameters is also needed.

Let's say we have an album with photos. And we are trying to convert album plain object to class object:

import { Exposed } from "class-transform";

class Photo {
  id = Exposed.number(); // number | null
  filename = Exposed.string("HI.jpg"); // string
}

class Album {
  id = Exposed.number(); // number | null
  name = Exposed.string(); // string | null
  tags = Exposed.strings(); // Array<string>
  photo = Exposed.struct(Photo, []); // Photo
  photos = Exposed.structs(Photo, []); // Array<Photo>
  hardCover = true;
}

let instance = new Album();
console.log(instance);
// Album {
//   id: null,
//   name: null,
//   tags: [],
//   photo: Photo {
//     id: null,
//     filename: 'HI.jpg',
//   },
//   photos: [
//     Photo {
//       id: null,
//       filename: 'HI.jpg'
//     },
//     Photo {
//       id: null,
//       filename: 'HI.jpg'
//     }
//   ],
//   hardCover: true
// }

Using different property name in plain objects

If the plain object's property should have a different name, you can do that by using Exposed.alias method. Please note that the type method should come at the end.

import { Exposed, plainToInstance, instanceToPlain } from "class-transform";

class User {
  firstName = Exposed.alias("first_name_raw").string();
  lastName = Exposed.alias("last_name_raw").string();
}

let plain = { first_name_raw: "John", last_name_raw: "Davis" };

let instance = plainToInstance(plain, User, []);
console.log(instance);
// User { firstName: 'John', lastName: 'Davis' }

let plainNew = instanceToPlain(instance);
console.log(plainNew);
// { first_name_raw: 'John', last_name_raw: 'Davis' }

This is useful when the JSON API uses snakecase or some other naming conventions.

Providing an initial value

When a field didn't receive some proper value, it can get an initial value instead of being filled with null. Simply provide the initial value to the type method. By doing so, null will be removed from the field's type hint.

import { Exposed, plainToInstance } from "class-transform";

class User {
  firstName = Exposed.string(); // string | null
  lastName = Exposed.string("Davis"); // string
}

let plain = { firstName: "John" };

let instance = plainToInstance(plain, User, []);
console.log(instance);
// User { firstName: 'John', lastName: 'Davis' }

Even when you provide an initial value of a wrong type, implicit type conversion happens under the hood, resulting in a completely type-safe instance.

Skipping by direction

You can control on which operation you will include a field. Use Exposed.toInstanceOnly or Exposed.toPlainOnly method.

import { Exclude } from "class-transformer";

class User {
  id = Exposed.number();
  email = Exposed.string();
  password = Exposed.toPlainOnly().string();
}

Now password field will be included only during the instanceToPlain operation.

  • toPlainOnly: Initial value on plainToInstance
  • toInstanceOnly: Drop on instanceToPlain

Using advanced types

Basically, it's recommended to store only primitive types for fields to maintain clean structure and transformation.

However, sometimes more advanced types might be needed. In such cases, you can use getter and setter methods to process the data from the basic values in the class.

import { Exposed, plainToInstance } from "class-transform";

class TimeRange {
  startTimestamp = Exposed.string();
  endTimestamp = Exposed.number();
  get start() {
    return new Date(this.startTimestamp ?? 0);
  }
  get end() {
    return new Date(this.endTimestamp ?? 0);
  }
}

let plain = {
  startTimestamp: "February 12, 2024 12:30:00",
  endTimestamp: 1613477400000,
};

let instance = plainToInstance(plain, TimeRange, []);
console.log(instance.start);
console.log(instance.end);
// 2024-02-12T03:30:00.000Z
// 2021-02-16T12:10:00.000Z

Implicit type conversion

Automatic conversion is provided for fields of primitive types.

import { Exposed, plainToInstance } from "class-transform";

class SomeType {
  prop = Exposed.number();
  otherProp = Exposed.string();
}

let plain = { prop: "1234", otherProp: 5678 };

let instance = plainToInstance(plain, SomeType, []);
console.log(instance);
//  { prop: 1234, otherProp: '5678' }

Constructing an instance manually

You can simply use the new keyword. All the Exposed fields will get its initial value.

import { Exposed } from "class-transform";

class Photo {
  id = Exposed.number();
  filename = Exposed.string("HELLO.jpg");
}

class Album {
  id = Exposed.number(0);
  name = Exposed.string();
  tags = Exposed.strings();
  photo = Exposed.struct(Photo, []);
  hardCover = true;

  /** @param {number} pages */
  constuctor(pages) {
    this.pages = pages;
  }
}

let instance = new Album(82);
console.log(instance);
// Album {
//   id: 0,
//   name: null,
//   tags: [],
//   photo: Photo {
//     id: null,
//     filename: 'HELLO.jpg',
//   },
//   hardCover: true,
//   pages: 82
// }