在JavaScript中, JSON.stringify()方法会寻找被序列化对象的toJSON方法. 如果对象中存在toJSON方法, 那么JSON.stringify会用经toJSON方法序列化后的对象来序列化.

举一个例子, 下面的代码会打印出与JSON.stringify({ answer: 42})一样的内容

const json = JSON.stringify({
  answer: { toJSON: () => 42 }
});

console.log(json); // {"answer":42}

在ES6的class中

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方法在developmentNODE_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方法能在这些模块中同样生效

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的类时不妨试一试.

文章来源于腾讯云开发者社区,点击查看原文