【译】什么是 JavaScript 中的 “toJSON()" 方法?

2,170 阅读2分钟

原文:What is the ‘toJSON()’ Function in JavaScript?

在 JavaScript 中,JSON.stringify() 函数会在被序列化的对象中查找名为 toJSON 的函数。如果一个对象有 toJSON 函数,JSON.stringify() 会调用 toJSON() 序列化 toJSON() 的返回值。

例如,下面的脚本打印的内容与 JSON.stringify({ answer: 42 }) 相同:

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

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

ES6 类

toJSON() 函数对于确保 ES6 类被正确序列化非常有用。例如,假设您有一个自定义的 JavaScript 错误类:

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}

但是,如果你在 HTTPError 类中添加了 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);
console.log(JSON.stringify(e)); // {"message":"Fail","status":404}

如果 NODE_ENVdevelopment 的,你甚至可以让 toJSON() 序列化堆栈跟踪。

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 实例和数组中的 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()。例如,Expressres.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 buffers 也有一个 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 documents 还有一个 toJSON() 函数,以确保 Mongoose documents 的内部状态不会出现在 JSON.stringify() 输出中。

继续

在 JavaScript 中构建类时,toJSON() 函数是一个重要的工具。它是你控制 JavaScript 如何将你的类序列化成 JSON 的方式。toJSON() 函数可以帮助你解决很多问题,比如确保日期或Node.js buffer 以正确的格式序列化你的应用程序。下次你写 ES6 类的时候可以试一试。