JavaScript 类对象如何转换为 JSON:全面指南
在 JavaScript 开发中,将类对象(Class Object)转换为 JSON 是常见的需求,例如数据序列化、API 请求或本地存储,由于 JavaScript 的类实例本质上是对象原型链的复杂结构,直接使用 JSON.stringify() 往往无法得到预期的结果,本文将详细解析类对象转 JSON 的常见问题及多种解决方案,帮助你灵活处理不同场景的转换需求。
为什么直接使用 JSON.stringify() 会失效?
JSON.stringify() 是 JavaScript 中将对象转换为 JSON 字符串的内置方法,但它对类对象的处理存在局限性,假设我们有一个简单的类:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
const user = new User('Alice', 25);
console.log(JSON.stringify(user)); // 输出: {"name":"Alice","age":25}
看起来这个例子能正常输出,但如果类包含方法、 Symbol 属性或不可序列化的值(如函数、Date 对象等),问题就会暴露:
class User {
constructor(name, age) {
this.name = name;
this.age = age;
this.greet = () => console.log(`Hello, ${this.name}`);
}
}
const user = new User('Bob', 30);
console.log(JSON.stringify(user)); // 输出: {"name":"Bob","age":30} —— greet 方法被忽略
JSON.stringify() 的核心规则是:只序列化对象自身的可枚举属性,且属性值必须是基本类型(string、number、boolean、null、object 或 array)。
- 类的方法(函数)、Symbol 属性、不可枚举属性会被自动忽略;
- Date 对象会被转换为字符串(但本质是
toISOString()的结果,可能不符合预期); - Map、Set、RegExp 等特殊对象会被转为空对象 ;
- 循环引用的对象会直接抛出错误。
解决方案:从简单到全面的处理方法
针对上述问题,以下是几种常见的类对象转 JSON 的解决方案,覆盖基础到复杂场景。
方法 1:手动定义 toJSON() 方法(推荐)
JSON.stringify() 会优先检查对象是否存在 toJSON() 方法,如果存在,则直接调用该方法返回的结果进行序列化,这是最符合 JSON 标准且灵活的方式。
实现步骤:
- 在类中定义
toJSON()方法,返回一个包含需要序列化的属性的对象; - 直接调用
JSON.stringify(),无需额外处理。
示例:
class User {
constructor(name, age, email) {
this.name = name;
this.age = age;
this.email = email;
this.password = '123456'; // 敏感信息,不希望序列化
}
// 定义 toJSON 方法
toJSON() {
return {
name: this.name,
age: this.age,
email: this.email
};
}
}
const user = new User('Charlie', 28, 'charlie@example.com');
console.log(JSON.stringify(user));
// 输出: {"name":"Charlie","age":28,"email":"charlie@example.com"}
// password 被自动排除
优点:
- 控制性强,可精确决定哪些属性被序列化;
- 符合 JSON 规范,是官方推荐的做法;
- 可处理复杂嵌套对象(只需在嵌套对象中也定义
toJSON())。
注意:
toJSON()应返回一个普通对象,避免返回类实例(可能导致无限递归)。
方法 2:使用 Object.keys() + reduce() 提取属性
如果不想修改类定义(例如第三方库的类),可以通过遍历对象自身的可枚举属性,手动构建一个适合序列化的对象。
实现步骤:
- 使用
Object.keys()获取对象自身的可枚举属性名数组; - 用
reduce()遍历属性名,筛选需要的属性并构建新对象。
示例:
class Product {
constructor(id, title, price, description) {
this.id = id;
this.title = title;
this.price = price;
this.description = description;
this.stock = 100; // 不希望序列化库存
}
}
const product = new Product(1, 'Laptop', 999, 'A high-performance laptop');
const productJson = Object.keys(product)
.filter(key => key !== 'stock') // 排除 stock 属性
.reduce((obj, key) => {
obj[key] = product[key];
return obj;
}, {});
console.log(JSON.stringify(productJson));
// 输出: {"id":1,"title":"Laptop","price":999,"description":"A high-performance laptop"}
优点:
- 无需修改类定义,适用于不可变动的类;
- 灵活筛选属性(可通过函数动态判断是否包含某属性)。
缺点:
- 需要手动维护属性列表,属性较多时代码冗长;
- 无法处理嵌套对象的复杂逻辑(如 Date 转字符串)。
方法 3:结合 JSON.stringify() 的 replacer 参数
JSON.stringify() 的第二个参数 replacer 可以是一个函数或数组,用于控制序列化过程,结合类实例,可以用 replacer 函数动态处理属性。
实现步骤:
- 定义
replacer函数,接收key和value,返回需要序列化的值; - 将类实例和
replacer传入JSON.stringify()。
示例:
class Employee {
constructor(name, position, salary) {
this.name = name;
this.position = position;
this.salary = salary;
this.id = 'EMP001'; // 敏感信息,排除
}
}
const employee = new Employee('David', 'Engineer', 8000);
const replacer = (key, value) => {
// 排除 id 属性
if (key === 'id') return undefined;
return value;
};
console.log(JSON.stringify(employee, replacer));
// 输出: {"name":"David","position":"Engineer","salary":8000}
更复杂的 replacer:处理 Date 对象
class LogEntry {
constructor(message, timestamp) {
this.message = message;
this.timestamp = new Date(timestamp);
}
}
const log = new Log('User login', '2023-10-01T12:00:00Z');
const dateReplacer = (key, value) => {
if (value instanceof Date) {
return value.toISOString(); // 将 Date 转为 ISO 字符串
}
return value;
};
console.log(JSON.stringify(log, dateReplacer));
// 输出: {"message":"User login","timestamp":"2023-10-01T12:00:00.000Z"}
优点:
- 无需修改类定义,直接在序列化时处理属性;
- 可动态处理特殊类型(如 Date、RegExp)。
缺点:
- 逻辑集中在
replacer函数中,复杂场景下可读性降低; - 无法直接过滤嵌套对象中的属性(需递归处理)。
方法 4:使用第三方库(如 class-transformer)
对于大型项目或复杂类结构(如嵌套对象、条件序列化),手动处理可能效率低下,此时可以使用第三方库 class-transformer,它提供了装饰器和方法,简化类对象与 JSON 的转换。
安装:
npm install class-transformer reflect-metadata
使用示例:
import { plainToClass, classToPlain, Exclude, Expose } from 'class-transformer';
import 'reflect-metadata';
class User {
constructor(name, age, password) {
this.name = name;
this.age = age;
this.password = password;
}
// 使用 @Expose 标记需要序列化的属性
@Expose()
name: string;
@Expose()
age: number;
// 使用 @Exclude 标记排除的属性
@Exclude()
password: string;
}
const user = new User('Eve', 22, 'secret');
const plainUser = classToPlain(user); // 类对象转普通对象
console.log(JSON.stringify(plainUser));
// 输出: {"name":"Eve","age":22}
// 反向:普通对象转类对象
const jsonUser = '{"name":"Frank","age":35,"password":"hidden"}';
const userInstance = plainToClass(User, JSON.parse(jsonUser));
console.log(userInstance); // User { name: 'Frank', age: 35, password: 'hidden' }
优点:
- 通过装饰器声明式管理序列化规则,代码清晰;
- 支持复杂场景(如嵌套转换、条件序列化、自定义转换逻辑);
- 提供双向转换(类↔普通对象)。
缺点:
- 需要引入额外依赖,增加项目体积;
- 需要学习装饰



还没有评论,来说两句吧...