In JavaScript, the JSON.stringify()
function
looks for functions named toJSON
in the object being serialized. If
an object has a toJSON
function, JSON.stringify()
calls toJSON()
and serializes the return value from toJSON()
instead.
For example, the below script prints the same thing as JSON.stringify({ answer: 42 })
.
const json = JSON.stringify({
answer: { toJSON: () => 42 }
});
console.log(json); // {"answer":42}
With ES6 Classes
The toJSON()
function is useful for making sure ES6 classes get
serialized correctly. For example, suppose you have a custom JavaScript
error class.
class HTTPError extends Error {
constructor(message, status) {
super(message);
this.status = status;
}
}
By default, JavaScript isn't great with serializing errors. The below
script prints {"status":404}
, no error message or stack trace.
class HTTPError extends Error {
constructor(message, status) {
super(message);
this.status = status;
}
}
const e = new HTTPError('Fail', 404);
console.log(JSON.stringify(e)); // {"status":404}
However, if you add a toJSON()
method to your HTTPError
class,
you can configure how JavaScript serializes instances of HTTPError
.
class HTTPError extends Error {
constructor(message, status) {
super(message);
this.status = status;
}
toJSON() {
return { message: this.message, status: this.status };
}
}
const e = new HTTPError('Fail', 404);
console.log(JSON.stringify(e)); // {"message":"Fail","status":404}
You can even get fancy and make toJSON()
serialize the stack trace
if the NODE_ENV
is development
.
class HTTPError extends Error {
constructor(message, status) {
super(message);
this.status = status;
}
toJSON() {
const ret = { message: this.message, status: this.status };
if (process.env.NODE_ENV === 'development') {
ret.stack = this.stack;
}
return ret;
}
}
const e = new HTTPError('Fail', 404);
// {"message":"Fail","status":404,"stack":"Error: Fail\n at ...
console.log(JSON.stringify(e));
The neat thing about toJSON()
is that JavaScript handles recursion for you,
so it will still correctly serialize deeply nested HTTPError
instances
and HTTPError
instances in arrays.
class HTTPError extends Error {
constructor(message, status) {
super(message);
this.status = status;
}
toJSON() {
return { message: this.message, status: this.status };
}
}
const e = new HTTPError('Fail', 404);
// {"nested":{"message":"Fail","status":404},"arr":[{"message":"Fail","status":404}]}
console.log(JSON.stringify({
nested: e,
arr: [e]
}));
Many libraries and frameworks use JSON.stringify()
under the hood.
For example, Express' res.json()
function and
Axios POST requests convert objects to
JSON using JSON.stringify()
. So custom toJSON()
functions work with those modules as well.
toJSON()
in the Wild
Many Node.js libraries and frameworks use toJSON()
to ensure JSON.stringify()
can serialize complex objects into something meaningful. For example, Moment.js objects have a nice simple toJSON()
function that looks like this:
function toJSON () {
// JSON.stringify(new Date(NaN)) === 'null'
return this.isValid() ? this.toISOString() : 'null';
}
You can try it yourself by running:
const moment = require('moment');
console.log(moment('2019-06-01').toJSON.toString());
Node.js buffers also
have a toJSON()
function.
const buf = Buffer.from('abc');
console.log(buf.toJSON.toString());
// Prints:
function toJSON() {
if (this.length > 0) {
const data = new Array(this.length);
for (var i = 0; i < this.length; ++i)
data[i] = this[i];
return { type: 'Buffer', data };
} else {
return { type: 'Buffer', data: [] };
}
}
Mongoose documents also have a toJSON()
function that ensures the internal state of Mongoose documents doesn't end up in JSON.stringify()
output.
Moving On
The toJSON()
function is an important tool when building classes in JavaScript.
It is how you control how JavaScript serializes your class into JSON. The
toJSON()
function can help you solve numerous problems, like making sure
dates or Node.js buffers get serialized in the right format for your app.
Give it a shot next time you write an ES6 class.