实现一个JSON.stringify

手写实现JSON.stringify方法,支持各种数据类型的序列化处理

问题

JSON.stringify 是 JavaScript 中用于将对象转换为 JSON 字符串的方法。需要实现一个自定义的 stringify 函数,能够正确处理各种数据类型,包括基本类型、对象、数组、特殊值等,并遵循 JSON.stringify 的转换规则。

解答

function myStringify(value, replacer = null, space = 0) {
  // 处理缩进空格
  let indent = '';
  if (typeof space === 'number' && space > 0) {
    indent = ' '.repeat(Math.min(space, 10));
  } else if (typeof space === 'string') {
    indent = space.slice(0, 10);
  }
  
  // 当前缩进级别
  let currentIndent = '';
  
  // 主序列化函数
  function serialize(val, depth = 0) {
    // 更新当前缩进
    currentIndent = indent.repeat(depth);
    const nextIndent = indent.repeat(depth + 1);
    
    // 处理 null
    if (val === null) {
      return 'null';
    }
    
    // 处理布尔值
    if (typeof val === 'boolean') {
      return String(val);
    }
    
    // 处理数字
    if (typeof val === 'number') {
      // NaN 和 Infinity 转换为 null
      return isFinite(val) ? String(val) : 'null';
    }
    
    // 处理字符串
    if (typeof val === 'string') {
      return `"${escapeString(val)}"`;
    }
    
    // 处理函数、undefined、Symbol - 在对象中会被忽略,在数组中转为 null
    if (typeof val === 'function' || typeof val === 'undefined' || typeof val === 'symbol') {
      return undefined;
    }
    
    // 处理 Date 对象
    if (val instanceof Date) {
      return `"${val.toISOString()}"`;
    }
    
    // 处理数组
    if (Array.isArray(val)) {
      const items = val.map(item => {
        const serialized = serialize(item, depth + 1);
        // undefined、函数、symbol 在数组中转为 null
        return serialized === undefined ? 'null' : serialized;
      });
      
      if (indent && items.length > 0) {
        return `[\n${nextIndent}${items.join(`,\n${nextIndent}`)}\n${currentIndent}]`;
      }
      return `[${items.join(',')}]`;
    }
    
    // 处理普通对象
    if (typeof val === 'object') {
      // 检测循环引用
      if (seen.has(val)) {
        throw new TypeError('Converting circular structure to JSON');
      }
      seen.add(val);
      
      const keys = replacer && Array.isArray(replacer) 
        ? replacer.filter(key => val.hasOwnProperty(key))
        : Object.keys(val);
      
      const pairs = [];
      for (const key of keys) {
        const value = val[key];
        const serialized = serialize(value, depth + 1);
        
        // 忽略 undefined、函数、symbol
        if (serialized !== undefined) {
          const keyStr = `"${escapeString(String(key))}"`;
          pairs.push(indent ? `${nextIndent}${keyStr}: ${serialized}` : `${keyStr}:${serialized}`);
        }
      }
      
      seen.delete(val);
      
      if (indent && pairs.length > 0) {
        return `{\n${pairs.join(',\n')}\n${currentIndent}}`;
      }
      return `{${pairs.join(',')}}`;
    }
    
    return undefined;
  }
  
  // 转义字符串中的特殊字符
  function escapeString(str) {
    const escapeMap = {
      '"': '\\"',
      '\\': '\\\\',
      '\b': '\\b',
      '\f': '\\f',
      '\n': '\\n',
      '\r': '\\r',
      '\t': '\\t'
    };
    
    return str.replace(/["\\\b\f\n\r\t]/g, char => escapeMap[char]);
  }
  
  // 用于检测循环引用
  const seen = new WeakSet();
  
  // 处理 replacer 函数
  if (typeof replacer === 'function') {
    value = replacer('', value);
  }
  
  const result = serialize(value);
  
  // 顶层的 undefined、函数、symbol 返回 undefined
  return result === undefined ? undefined : result;
}

使用示例

// 基本类型
console.log(myStringify(123)); // "123"
console.log(myStringify('hello')); // "\"hello\""
console.log(myStringify(true)); // "true"
console.log(myStringify(null)); // "null"

// 特殊值
console.log(myStringify(undefined)); // undefined
console.log(myStringify(NaN)); // "null"
console.log(myStringify(Infinity)); // "null"

// 对象
const obj = {
  name: 'John',
  age: 30,
  active: true,
  score: null
};
console.log(myStringify(obj));
// {"name":"John","age":30,"active":true,"score":null}

// 数组
const arr = [1, 'test', true, null, undefined, NaN];
console.log(myStringify(arr));
// [1,"test",true,null,null,null]

// 嵌套对象
const nested = {
  user: {
    name: 'Alice',
    hobbies: ['reading', 'coding']
  },
  count: 42
};
console.log(myStringify(nested));
// {"user":{"name":"Alice","hobbies":["reading","coding"]},"count":42}

// 使用缩进
console.log(myStringify(nested, null, 2));
// {
//   "user": {
//     "name": "Alice",
//     "hobbies": [
//       "reading",
//       "coding"
//     ]
//   },
//   "count": 42
// }

// Date 对象
console.log(myStringify(new Date('2024-01-01')));
// "2024-01-01T00:00:00.000Z"

// 忽略函数和 undefined
const withFunc = {
  name: 'test',
  fn: function() {},
  undef: undefined,
  value: 123
};
console.log(myStringify(withFunc));
// {"name":"test","value":123}

// 循环引用检测
const circular = { name: 'test' };
circular.self = circular;
try {
  myStringify(circular);
} catch (e) {
  console.log(e.message); // "Converting circular structure to JSON"
}

关键点

  • 类型判断顺序:按照 null、boolean、number、string、function/undefined/symbol、Date、Array、Object 的顺序进行判断,确保特殊类型优先处理

  • 特殊值处理:NaN 和 Infinity 转换为 null;undefined、函数、Symbol 在对象中被忽略,在数组中转为 null;顶层返回 undefined

  • 字符串转义:正确转义双引号、反斜杠、换行符等特殊字符,确保生成的 JSON 字符串合法

  • 循环引用检测:使用 WeakSet 记录已访问的对象,检测到循环引用时抛出 TypeError 异常

  • 缩进格式化:支持 space 参数,可以是数字(空格数)或字符串,实现美化输出的 JSON 格式

  • Date 对象处理:Date 对象调用 toISOString() 方法转换为 ISO 8601 格式的字符串

  • replacer 支持:支持数组形式的 replacer,用于过滤对象的键(函数形式的 replacer 可进一步扩展)

  • 递归序列化:通过递归处理嵌套的对象和数组,并正确管理缩进层级