Unleashing the power of toJSON method in JavaScript
The toJSON
method is used to override the default serialization behavior of an Object, Array or Function in Javascript, serialized using the JSON.stringify
static method. Suppose you have a user object or class in your Node.js backend project, you will want to remove the password property when sending this object to the client.
In some cases, you might want to add a new property to an object during serialization or send a custom Error object with a message property to the client, all these can be done automatically by adding a toJSON
method to the object.
Prerequisite
We recommend having at least a basic knowledge of Javascript to follow along.
Major usages of the toJSON method
When JSON.stringify
is called on a Javascript object or ES6 Class instance;
it looks for a method called
toJSON
inside this object or class instance.If the object or class instance contains the
toJSON
method,JSON.stringify
calls it.When
toJSON
method returns an object.JSON.stringify
performs the above process recursively for properties in the returned object whose values in the current object or class instance are objects.All the return values are then serialized.
Adding new property/properties to serialized Javascript objects or class instances:
For Javascript Objects:
const user = {
firstName: 'John',
lastName: 'Doe',
email: 'johndoe@gmail.com',
password: 'password',
toJSON() {
return {
email: this.email,
fullName: this.firstName + ' ' + this.lastName
}
}
}
console.log(JSON.stringify(user));
//{"email":"johndoe@gmail.com","fullName":"John Doe"}
For Javascript ES6 Class Instance:
class User {
constructor(firstName, lastName, email, password) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.password = password;
}
toJSON() {
const {email, firstName, lastName} = this;
return {
email,
fullName: firstName + ' ' + lastName
}
}
}
console.log(JSON.stringify(new User('John', 'Doe', 'johndoe@gmail.com', 'password')));
// {"email":"johndoe@gmail.com","fullName":"John Doe"}
Removing existing property/properties from objects or class instances during serialization.
For Javascript Objects:
const book = {
title: 'User guide',
author: 'Herry Lam',
published_year: 1992,
toJSON() {
delete this.published_year;
return this;
}
}
console.log(JSON.stringify(book));
// {"title":"User guide","author":"Herry Lam"}
For Javascript ES6 Class Instance:
class User {
constructor(name, email, password) {
this.name = name;
this.email = email;
this.password = password;
this.order = null;
}
toJSON() {
delete this.password;
return this;
}
}
console.log(JSON.stringify(new User('Mary Doe', 'marydoe@gmail.com', 'password')));
// {"name":"Mary Doe","email":"marydoe@gmail.com"}
Practical usage with Mongoose Schema:
import {Schema} from 'mongoose';
const userSchema = new Schema({
email: {
required: true,
type: String
},
name: {
type: String,
required: true
},
password: {
type: String
}
}, {
timestamps: true
})
userSchema.method('toJSON', function (){
const userObject = this.toObject();
delete userObject.password;
return userObject;
})
More complex usage involving serializing nested objects and arrays
The code below models a user with an order containing some products. The user's password and the product's rating will be removed when an instance of the User
class is serialized.
class User {
constructor(name, email, password) {
this.name = name;
this.email = email;
this.password = password;
this.order = null;
}
toJSON() {
delete this.password;
return this;
}
}
class Order {
constructor(name) {
this.name = name;
this.products = [];
this.total = 0;
}
addProducts(products) {
this.products = this.products.concat(products);
let total = 0;
if(Array.isArray(products)){
total = products.reduce((acc, product) => acc + product.price, 0);
}else {
total = products.price;
}
this.total += total;
}
}
class Product {
constructor(name, price, rating) {
this.name = name;
this.price = price;
this.rating = rating || 0;
}
toJSON() {
delete this.rating;
return this;
}
}
const order = new Order('Order 1');
const firstProduct = new Product('Shoe', 20, 2);
const secondProduct = new Product('Shirt', 50, 3);
order.addProduct([firstProduct, secondProduct]);
const user = new User('John Doe', 'johndoe@gmail.com', 'password');
user.order = order;
console.log(JSON.stringify(user));
// {"name":"John Doe","email":"johndoe@gmail.com","order":{"name":"Order 1","products":[{"name":"Shoe","price":20},{"name":"Shirt","price":50}],"total":70}}
Serializing Custom Errors in Javascript
By default, Javascript doesn't serialize custom Errors properly. The code below prints {"code": 500}
with no error message or stack trace.
class RequestError extends Error {
constructor(message, code = 500) {
super(message);
this.code = code;
}
}
console.log(JSON.stringify(new RequestError('An error occured')));
// {"code": 500}
We can override this default serialization behavior by adding a toJSON method.
class RequestError extends Error {
constructor(message, code = 500) {
super(message);
this.code = code;
}
toJSON() {
const {message, code} = this;
return {
message,
code
}
}
}
console.log(JSON.stringify(new RequestError('An error occured')));
// {"message":"An error occurred","code":500}
Conclusion
The toJSON
method as seen above can be used to customize the serialization of objects and class instances in Javascript, JSON.stringify
also calls the toJSON
method recursively inside child objects or class instances which makes it very powerful.