JavaScript JSON 序列化

深入解析 JSON.stringify 的完整行为、toJSON 自定义序列化、JSON.parse 错误处理、structuredClone 深拷贝,以及序列化边界情况处理。

为什么理解 JSON 序列化是数据处理的基础

前后端数据传输、状态管理、本地存储——几乎所有前端数据处理场景都离不开 JSON。理解 JSON.stringify 和 JSON.parse 的完整行为,才能避免序列化相关的 bug。

这篇文章深入解析 JavaScript 的 JSON 序列化机制,包括常见陷阱和高级用法。


一、JSON.stringify

1.1 基本用法

const obj = { name: 'Alice', age: 30 };
const json = JSON.stringify(obj);
console.log(json); // '{"name":"Alice","age":30}'

// 反序列化
const parsed = JSON.parse(json);
console.log(parsed); // { name: 'Alice', age: 30 }

1.2 第二个参数:replacer

replacer 可以是函数或数组:

// 函数 replacer
const obj = { name: 'Alice', age: 30, password: 'secret' };

const filtered = JSON.stringify(obj, (key, value) => {
  if (key === 'password') return undefined; // 排除密码
  return value;
});
console.log(filtered); // '{"name":"Alice","age":30}'

// 数组 replacer — 只包含指定键
const partial = JSON.stringify(obj, ['name', 'age']);
console.log(partial); // '{"name":"Alice","age":30}'

1.3 第三个参数:space

const obj = { name: 'Alice', items: [1, 2, 3] };

// 数字 space:缩进
console.log(JSON.stringify(obj, null, 2));
/*
{
  "name": "Alice",
  "items": [
    1,
    2,
    3
  ]
}
*/

// 字符串 space:键名前缀
console.log(JSON.stringify(obj, null, '\t'));
// 使用 tab 缩进

// 自定义前缀
console.log(JSON.stringify(obj, null, '>>'));
/*
>>
>>"name": "Alice",
>>"items": [
>>>>1,
>>>>2,
>>>>3
>>]
}
*/

二、toJSON 自定义序列化

2.1 toJSON 方法

对象可以实现 toJSON 方法自定义序列化:

const user = {
  name: 'Alice',
  age: 30,
  password: 'secret',

  toJSON() {
    return {
      name: this.name,
      age: this.age
      // 排除 password
    };
  }
};

console.log(JSON.stringify(user));
// '{"name":"Alice","age":30}'

2.2 常见应用:日期序列化

const event = {
  title: 'Meeting',
  date: new Date('2024-04-08T10:00:00Z'),
  // Date 有 toJSON,返回 ISO 字符串
};

console.log(JSON.stringify(event));
// '{"title":"Meeting","date":"2024-04-08T10:00:00.000Z"}'

// 自定义日期格式
const event2 = {
  title: 'Meeting',
  date: {
    toJSON: () => '2024-04-08'
  }
};

三、JSON.parse

3.1 基本用法

const json = '{"name":"Alice","age":30}';
const obj = JSON.parse(json);
console.log(obj.name); // 'Alice'

3.2 reviver 参数

reviver 函数在解析时转换值:

const json = '{"createdAt":"2024-04-08T10:00:00Z","score":100}';

// 转换日期字符串为 Date 对象
const obj = JSON.parse(json, (key, value) => {
  if (key === 'createdAt') {
    return new Date(value);
  }
  return value;
});

console.log(obj.createdAt instanceof Date); // true
console.log(obj.score); // 100

3.3 错误处理

// 无效 JSON 会抛出 SyntaxError
try {
  JSON.parse('{invalid}');
} catch (e) {
  console.log(e instanceof SyntaxError); // true
  console.log(e.message); // JSON.parse error
}

// 安全解析函数
function safeJsonParse(str, reviver) {
  try {
    return { success: true, data: JSON.parse(str, reviver) };
  } catch (e) {
    return { success: false, error: e.message };
  }
}

const result = safeJsonParse('{invalid}');
console.log(result.success); // false

四、序列化边界情况

4.1 undefined、函数、Symbol

const obj = {
  a: 1,
  b: undefined,
  c: function() {},
  d: Symbol('test'),
  e: ['x', undefined, 'y']
};

console.log(JSON.stringify(obj));
// '{"a":1,"e":["x",null,"y"]}'

// 规则:
// - undefined → 忽略(数组中变为 null)
// - 函数 → 忽略
// - Symbol → 忽略

4.2 NaN 和 Infinity

console.log(JSON.stringify({ value: NaN })); // '{"value":null}'
console.log(JSON.stringify({ value: Infinity })); // '{"value":null}'
console.log(JSON.stringify({ value: -Infinity })); // '{"value":null}'

4.3 循环引用

const obj = { name: 'Alice' };
obj.self = obj;

try {
  JSON.stringify(obj);
} catch (e) {
  console.log(e.message); // Converting circular reference to JSON
}

// 解决方案:检测循环引用
function stringifyCircular(obj) {
  const seen = new WeakSet();
  return JSON.stringify(obj, (key, value) => {
    if (typeof value === 'object' && value !== null) {
      if (seen.has(value)) {
        return '[Circular]';
      }
      seen.add(value);
    }
    return value;
  });
}

4.4 BigInt

// BigInt 无法序列化
try {
  JSON.stringify({ value: 42n });
} catch (e) {
  console.log(e.message); // Do not know how to serialize a BigInt
}

// 解决方案:转换为字符串
function replacer(key, value) {
  if (typeof value === 'bigint') {
    return value.toString();
  }
  return value;
}

五、structuredClone

5.1 原生深拷贝

ES2021 引入 structuredClone,提供原生深拷贝:

const original = {
  name: 'Alice',
  nested: { value: 1 },
  date: new Date(),
  map: new Map([['a', 1], ['b', 2]])
};

const clone = structuredClone(original);

// 修改克隆不影响原对象
clone.nested.value = 100;
console.log(original.nested.value); // 1

5.2 structuredClone 的优势

// 支持更多类型
const obj = {
  date: new Date(),
  map: new Map(),
  set: new Set(),
  arrayBuffer: new ArrayBuffer(8),
  error: new Error('test')
};

const clone = structuredClone(obj);

// structuredClone vs JSON.stringify
// - JSON.stringify 无法处理函数、undefined、Symbol、BigInt
// - structuredClone 支持更多内置类型
// - 结构化克隆保留对象类型

5.3 structuredClone 的限制

// 无法克隆函数
try {
  structuredClone({ fn: () => {} });
} catch (e) {
  console.log(e); // DataCloneError
}

// 无法克隆 DOM 节点
try {
  structuredClone(document.body);
} catch (e) {
  console.log(e); // DataCloneError
}

// 无法克隆循环引用(但可以克隆某些循环引用结构)
const obj = { name: 'test' };
obj.self = obj;
// structuredClone(obj); // DataCloneError

六、延展阅读