在JavaScript中, JSON.stringify()
方法会寻找被序列化对象的toJSON
方法. 如果对象中存在toJSON
方法, 那么JSON.stringify
会用经toJSON
方法序列化后的对象来序列化.
举一个例子, 下面的代码会打印出与JSON.stringify({ answer: 42})
一样的内容
const json = JSON.stringify({
answer: { toJSON: () => 42 }
});
console.log(json); // {"answer":42}
toJSON
方法对于正确地序列化一个ES6的class具有很重要的意义. 举个例子, 假设你有一个自定的JavaScript的Error类
class HTTPError extends Error {
constructor(message, status) {
super(message);
this.status = status;
}
}
一般情况下, JavaScript对于错误的序列化并不是十分优秀. 下面的代码中会打印{"status": 404}
, 没有错误信息也没有堆栈信息
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}
但是当你添加了一个toJSON
方法在HTTPError
类里面后, 你就可以控制JavaScript如何来序列化这个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}
除此之外还能再整一些活来让toJSON
方法在development
的NODE_ENV
下额外带上堆栈信息
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));
toJSON
还有一个好处就是JavaScript能够处理递归, 因此它能够正确地序列化那些具有深层次嵌套的或者在数组中的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);
// {"nested":{"message":"Fail","status":404},"arr":[{"message":"Fail","status":404}]}
console.log(JSON.stringify({
nested: e,
arr: [e]
}));
许多的库与框架都是用了JSON.stringify()
这个方法. 举个例子, Express的res.json()
与Axios的POST请求会是用JSON.stringify()
方法来将对象转换为JSON. 因此, 自定义的toJSON
方法能在这些模块中同样生效
许多Node.js的库与框架使用toJSON
来保障JSON.stringify
方法能够正确地将复杂的对象序列化为具有意义的东西. 举个例子, Moment.js
对象就有一个简单的toJSON
方法
function toJSON () {
// JSON.stringify(new Date(NaN)) === 'null'
return this.isValid() ? this.toISOString() : 'null';
}
自己试一试的话可以试试这段代码
const moment = require('moment');
console.log(moment('2019-06-01').toJSON.toString());
Node.js的buffer也有这样的toJSON
方法
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的文档也有toJSON
方法来保证Mongoose文档的内部会状态不会跑到JSON.stringify
的结果里面去
toJSON
方法在构建一个JavaScript类时是一个十分重要的工具. 这可以控制JavaScript类如何序列化为JSON. toJSON
能够帮助开发者解决不少问题, 例如保证buffer能够正确地转化为正确地数据类型等. 下次写ES6的类时不妨试一试.