TL;DR - Use class-transformer to transform JSON object to class instance.
I started working with TypeScript about two years ago. Most of the time I read a JSON object from a remote REST server. This JSON object has all the properties of a TypeScript class. There is a question always buzz my head: How do I cast/parse the received JSON object to an instance of a corresponding class?.
Over time, I found this class-transformer library is super helpful. You can install this into your project and start using it from now on to see the difference. They supported nested property too so you don’t have to worry about that.
It is worth mentioning that not all the time we need to cast from JSON object to a class, but sometimes it is really helpful. As per the example above, If I don’t have the method getFullName
in the instance of class, I could create a new util method that takes a plain User
object as argument and return the expected result. In my projects, many times I used visitor pattern for handling different concrete classes, and it is required the instance of each class to be working. It won’t work for a plain object.
The decision is yours. Put all properties and methods inside a single class is the encapsulation in Object-Oriented Programming (OOP) while Functional Programming treated everything as a function.
The below section was quoted from their readme.
In JavaScript there are two types of objects:
Plain objects are objects that are instances of Object
class.
Sometimes they are called literal objects, when created via {}
notation. E.g var obj = {}
Class objects are instances of classes with their 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 the ES6 classes you have.
For example, if you are loading a JSON from your backend, some API, or a JSON file. After you JSON.parse
, it gives you a plain JavaScript object, not an instance of a class you have.
For example you have a list of users that you received from the server:
[
{
"id": 1,
"firstName": "Johny",
"lastName": "Cage",
"age": 27
},
{
"id": 2,
"firstName": "Ismoil",
"lastName": "Somoni",
"age": 50
},
{
"id": 3,
"firstName": "Luke",
"lastName": "Dacascos",
"age": 12
}
]
And you have a User
class defined on client side:
export class User {
id: number
firstName: string
lastName: string
age: number
getFullName() {
return this.firstName + ' ' + this.lastName
}
isAdult() {
return this.age > 36 && this.age < 60
}
}
You are assuming that you are downloading users of type User
from users.json
file and may want to write
following code:
fetch('/api/users').then((users: User[]) => {
// you can use users here, and type hinting also will be available to you,
// but users are not actually instances of User class
// this means that you can't use methods of User class
})
In this code you can use users[0].id
, you can also use users[0].firstName
and users[0].lastName
.
However you cannot use users[0].getFullName()
or users[0].isAdult()
because “users” actually is
array of plain javascript objects, not instances of User object.
You lied to the compiler when you said that its users: User[]
.
So what to do? How to make a users
array of instances of User
objects instead of plain javascript objects?
The solution is to create new instances of User object and manually copy all properties to new objects.
But things may go wrong very fast once you have a more complex object hierarchy.
Alternatives? Yes, you can use class-transformer. The purpose of this library is to help you to map you plain javascript objects to the instances of classes you have.
This library also great for models exposed in your APIs, because it provides a great tooling to control what your models are exposing in your API. Here is example how it will look like:
fetch('/api/users').then((users: Object[]) => {
const realUsers = plainToClass(User, users)
// now each user in realUsers is instance of User class
})
Now you can use users[0].getFullName()
and users[0].isAdult()
methods.
When you are trying to transform objects that have nested objects, its required to know what type of object you are trying to transform. Since Typescript does not have good reflection abilities yet, we should implicitly specify what type of object each property contains. This is done using @Type
decorator.
Let’s say we have an album with photos. And we are trying to convert album plain object to class object:
import { Type, plainToClass } from 'class-transformer'
export class Album {
id: number
name: string
@Type(() => Photo)
photos: Photo[]
}
export class Photo {
id: number
filename: string
}
let album = plainToClass(Album, albumJson)
// now album is Album object with Photo objects inside