手写JavaScript对象转JSON方法:从原理到实现
在JavaScript开发中,将对象转换为JSON字符串是一个常见的需求,虽然现代JavaScript提供了内置的JSON.stringify()方法来处理这个任务,但理解其底层原理并手写一个转换函数,不仅能加深对JSON序列化过程的理解,还能帮助我们处理一些特殊情况,本文将详细介绍如何手写一个将JavaScript对象转换为JSON字符串的函数。
JSON序列化的基本原理
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它基于JavaScript的一个子集,将JavaScript对象转换为JSON字符串需要遵循以下规则:
-
基本类型处理:
- 数字、布尔值直接转换
- 字符串需要用双引号包围
- null转换为"null"
- undefined会被忽略或转换为"undefined"(取决于实现)
-
对象处理:
- 属性名必须用双引号包围
- 属性值按上述规则转换
- 属性顺序可能不保留(ES6后规范要求保留)
-
数组处理:
- 元素按上述规则转换
- 保持数组顺序
-
其他情况:
- 循环引用需要处理(避免无限递归)
- 函数、Symbol等特殊类型通常会被忽略
手写JSON序列化函数的实现
下面我们逐步实现一个功能完善的stringify函数:
基础版本
function stringify(obj) {
// 处理基本类型
if (obj === null) {
return 'null';
}
if (typeof obj === 'string') {
return `"${obj}"`;
}
if (typeof obj === 'number' || typeof obj === 'boolean') {
return String(obj);
}
// 处理数组
if (Array.isArray(obj)) {
const elements = obj.map(item => stringify(item));
return `[${elements.join(',')}]`;
}
// 处理对象
if (typeof obj === 'object') {
const keys = Object.keys(obj);
const keyValuePairs = keys.map(key => {
const value = stringify(obj[key]);
// 忽略undefined值
if (value !== undefined) {
return `"${key}":${value}`;
}
return undefined;
}).filter(pair => pair !== undefined);
return `{${keyValuePairs.join(',')}}`;
}
// 其他类型(如function、symbol)返回undefined
return undefined;
}
增强版本(处理循环引用和更多选项)
function stringify(obj, replacer, space, seen = new WeakMap()) {
// 处理循环引用
if (seen.has(obj)) {
return '[Circular]';
}
// 处理基本类型
if (obj === null) {
return 'null';
}
if (typeof obj === 'string') {
return `"${obj.replace(/["\\]/g, '\\$&').replace(/\n/g, '\\n')}"`;
}
if (typeof obj === 'number' || typeof obj === 'boolean') {
return String(obj);
}
// 处理数组
if (Array.isArray(obj)) {
seen.set(obj, true);
const elements = obj.map(item => stringify(item, replacer, space, seen));
seen.delete(obj);
return `[${elements.join(',')}]`;
}
// 处理对象
if (typeof obj === 'object') {
seen.set(obj, true);
// 处理replacer函数
let processedObj = obj;
if (typeof replacer === 'function') {
processedObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = replacer.call(obj, key, obj[key]);
if (value !== undefined) {
processedObj[key] = value;
}
}
}
} else {
processedObj = obj;
}
const keys = typeof replacer === 'function'
? Object.keys(processedObj)
: (Array.isArray(replacer) ? replacer : Object.keys(processedObj));
const keyValuePairs = keys.map(key => {
const value = stringify(processedObj[key], replacer, space, seen);
// 忽略undefined值
if (value !== undefined) {
return `"${key}":${value}`;
}
return undefined;
}).filter(pair => pair !== undefined);
seen.delete(obj);
return `{${keyValuePairs.join(',')}}`;
}
// 其他类型(如function、symbol)返回undefined
return undefined;
}
完整版本(支持缩进格式化)
function stringify(obj, replacer, space, seen = new WeakMap()) {
// 处理循环引用
if (seen.has(obj)) {
return '[Circular]';
}
// 处理基本类型
if (obj === null) {
return 'null';
}
if (typeof obj === 'string') {
return `"${obj.replace(/["\\]/g, '\\$&').replace(/\n/g, '\\n')}"`;
}
if (typeof obj === 'number' || typeof obj === 'boolean') {
return String(obj);
}
// 处理space参数
let indent = '';
let newline = '';
if (typeof space === 'number') {
if (space > 0) {
indent = ' '.repeat(space);
newline = '\n';
}
} else if (typeof space === 'string') {
if (space.length > 0) {
indent = space;
newline = '\n';
}
}
// 处理数组
if (Array.isArray(obj)) {
seen.set(obj, true);
if (obj.length === 0) {
return '[]';
}
const elements = obj.map(item => {
const str = stringify(item, replacer, space, seen);
return newline + indent + str;
});
seen.delete(obj);
return `[${newline}${indent}${elements.join(`,${newline}${indent}`)}${newline}]`;
}
// 处理对象
if (typeof obj === 'object') {
seen.set(obj, true);
// 处理replacer函数
let processedObj = obj;
if (typeof replacer === 'function') {
processedObj = {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = replacer.call(obj, key, obj[key]);
if (value !== undefined) {
processedObj[key] = value;
}
}
}
} else {
processedObj = obj;
}
const keys = typeof replacer === 'function'
? Object.keys(processedObj)
: (Array.isArray(replacer) ? replacer : Object.keys(processedObj));
if (keys.length === 0) {
return '{}';
}
const keyValuePairs = keys.map(key => {
const value = stringify(processedObj[key], replacer, space, seen);
// 忽略undefined值
if (value !== undefined) {
return `"${key}":${newline}${indent}${value}`;
}
return undefined;
}).filter(pair => pair !== undefined);
seen.delete(obj);
return `{${newline}${indent}${keyValuePairs.join(`,${newline}${indent}`)}${newline}}`;
}
// 其他类型(如function、symbol)返回undefined
return undefined;
}
使用示例
const obj = {
name: "John",
age: 30,
isStudent: false,
courses: ["Math", "Science"],
address: {
city: "New York",
zip: "10001"
},
sayHello: function() {
console.log("Hello");
},
[Symbol('id')]: 123
};
console.log(stringify(obj));
// 输出: {"name":"John","age":30,"isStudent":false,"courses":["Math","Science"],"address":{"city":"New York","zip":"10001"}}
// 带缩进的格式化
console.log(stringify(obj, null, 2));
/*
输出:
{
"name": "John",
"age": 30,
"isStudent": false,
"courses": [
"Math",
"Science"
],
"address": {
"city": "New York",
"zip": "10001"
}
}
*/
// 使用replacer函数
console.log(stringify(obj, (key, value) => {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value;
}));
// 输出: {"name":"JOHN","age":30,"isStudent":false,"courses":["MATH","SCIENCE"],"address":{"city":"NEW YORK","zip":"10001"}}
与原生JSON.stringify()的区别
虽然我们手写的stringify函数实现了基本功能,但与原生的JSON.stringify()相比,仍有一些差异:



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