实现一个JSON.parse

手写实现JSON字符串解析函数,将JSON格式字符串转换为JavaScript对象

问题

实现一个自定义的JSON解析函数,能够将符合JSON格式的字符串转换为对应的JavaScript数据类型(对象、数组、字符串、数字、布尔值、null等)。这道题考察对JSON格式规范的理解以及字符串解析能力。

解答

/**
 * 实现JSON.parse - 方法一:使用eval(不推荐)
 * 注意:eval存在安全风险,仅用于理解原理
 */
function jsonParse1(jsonStr) {
  return eval('(' + jsonStr + ')');
}

/**
 * 实现JSON.parse - 方法二:使用Function构造函数(相对安全)
 */
function jsonParse2(jsonStr) {
  return new Function('return ' + jsonStr)();
}

/**
 * 实现JSON.parse - 方法三:手动解析(推荐)
 * 完整实现词法分析和语法分析
 */
function jsonParse(jsonStr) {
  let i = 0; // 当前解析位置
  
  // 解析值的入口函数
  function parseValue() {
    skipWhitespace();
    const char = jsonStr[i];
    
    if (char === '{') return parseObject();
    if (char === '[') return parseArray();
    if (char === '"') return parseString();
    if (char === 't') return parseTrue();
    if (char === 'f') return parseFalse();
    if (char === 'n') return parseNull();
    if (char === '-' || (char >= '0' && char <= '9')) return parseNumber();
    
    throw new SyntaxError(`Unexpected token at position ${i}`);
  }
  
  // 跳过空白字符
  function skipWhitespace() {
    while (i < jsonStr.length && /\s/.test(jsonStr[i])) {
      i++;
    }
  }
  
  // 解析对象
  function parseObject() {
    const obj = {};
    i++; // 跳过 '{'
    skipWhitespace();
    
    // 空对象
    if (jsonStr[i] === '}') {
      i++;
      return obj;
    }
    
    while (i < jsonStr.length) {
      skipWhitespace();
      
      // 解析键(必须是字符串)
      if (jsonStr[i] !== '"') {
        throw new SyntaxError(`Expected string key at position ${i}`);
      }
      const key = parseString();
      
      skipWhitespace();
      
      // 解析冒号
      if (jsonStr[i] !== ':') {
        throw new SyntaxError(`Expected ':' at position ${i}`);
      }
      i++;
      
      // 解析值
      const value = parseValue();
      obj[key] = value;
      
      skipWhitespace();
      
      // 检查是否结束或继续
      if (jsonStr[i] === '}') {
        i++;
        return obj;
      }
      
      if (jsonStr[i] === ',') {
        i++;
        continue;
      }
      
      throw new SyntaxError(`Expected ',' or '}' at position ${i}`);
    }
    
    throw new SyntaxError('Unexpected end of JSON input');
  }
  
  // 解析数组
  function parseArray() {
    const arr = [];
    i++; // 跳过 '['
    skipWhitespace();
    
    // 空数组
    if (jsonStr[i] === ']') {
      i++;
      return arr;
    }
    
    while (i < jsonStr.length) {
      const value = parseValue();
      arr.push(value);
      
      skipWhitespace();
      
      if (jsonStr[i] === ']') {
        i++;
        return arr;
      }
      
      if (jsonStr[i] === ',') {
        i++;
        continue;
      }
      
      throw new SyntaxError(`Expected ',' or ']' at position ${i}`);
    }
    
    throw new SyntaxError('Unexpected end of JSON input');
  }
  
  // 解析字符串
  function parseString() {
    let str = '';
    i++; // 跳过开始的 '"'
    
    while (i < jsonStr.length) {
      const char = jsonStr[i];
      
      if (char === '"') {
        i++;
        return str;
      }
      
      // 处理转义字符
      if (char === '\\') {
        i++;
        const escapeChar = jsonStr[i];
        const escapeMap = {
          '"': '"',
          '\\': '\\',
          '/': '/',
          'b': '\b',
          'f': '\f',
          'n': '\n',
          'r': '\r',
          't': '\t'
        };
        
        if (escapeChar in escapeMap) {
          str += escapeMap[escapeChar];
          i++;
        } else if (escapeChar === 'u') {
          // Unicode转义
          i++;
          const unicode = jsonStr.substr(i, 4);
          str += String.fromCharCode(parseInt(unicode, 16));
          i += 4;
        } else {
          throw new SyntaxError(`Invalid escape character at position ${i}`);
        }
      } else {
        str += char;
        i++;
      }
    }
    
    throw new SyntaxError('Unterminated string');
  }
  
  // 解析数字
  function parseNumber() {
    let numStr = '';
    
    if (jsonStr[i] === '-') {
      numStr += '-';
      i++;
    }
    
    // 整数部分
    while (i < jsonStr.length && jsonStr[i] >= '0' && jsonStr[i] <= '9') {
      numStr += jsonStr[i];
      i++;
    }
    
    // 小数部分
    if (jsonStr[i] === '.') {
      numStr += '.';
      i++;
      while (i < jsonStr.length && jsonStr[i] >= '0' && jsonStr[i] <= '9') {
        numStr += jsonStr[i];
        i++;
      }
    }
    
    // 指数部分
    if (jsonStr[i] === 'e' || jsonStr[i] === 'E') {
      numStr += jsonStr[i];
      i++;
      if (jsonStr[i] === '+' || jsonStr[i] === '-') {
        numStr += jsonStr[i];
        i++;
      }
      while (i < jsonStr.length && jsonStr[i] >= '0' && jsonStr[i] <= '9') {
        numStr += jsonStr[i];
        i++;
      }
    }
    
    return parseFloat(numStr);
  }
  
  // 解析true
  function parseTrue() {
    if (jsonStr.substr(i, 4) === 'true') {
      i += 4;
      return true;
    }
    throw new SyntaxError(`Invalid token at position ${i}`);
  }
  
  // 解析false
  function parseFalse() {
    if (jsonStr.substr(i, 5) === 'false') {
      i += 5;
      return false;
    }
    throw new SyntaxError(`Invalid token at position ${i}`);
  }
  
  // 解析null
  function parseNull() {
    if (jsonStr.substr(i, 4) === 'null') {
      i += 4;
      return null;
    }
    throw new SyntaxError(`Invalid token at position ${i}`);
  }
  
  return parseValue();
}

使用示例

// 示例1:解析基本类型
console.log(jsonParse('123'));           // 123
console.log(jsonParse('"hello"'));       // "hello"
console.log(jsonParse('true'));          // true
console.log(jsonParse('null'));          // null

// 示例2:解析对象
const objStr = '{"name":"张三","age":25,"isStudent":false}';
console.log(jsonParse(objStr));
// { name: '张三', age: 25, isStudent: false }

// 示例3:解析数组
const arrStr = '[1, 2, 3, "test", true, null]';
console.log(jsonParse(arrStr));
// [1, 2, 3, "test", true, null]

// 示例4:解析嵌套结构
const complexStr = `{
  "user": {
    "name": "李四",
    "hobbies": ["reading", "coding"],
    "address": {
      "city": "北京",
      "code": 100000
    }
  },
  "count": 42
}`;
console.log(jsonParse(complexStr));

// 示例5:解析带转义字符的字符串
const escapeStr = '{"text":"Hello\\nWorld\\t\\"Quote\\""}';
console.log(jsonParse(escapeStr));
// { text: 'Hello\nWorld\t"Quote"' }

// 示例6:解析科学计数法
console.log(jsonParse('1.23e10'));       // 12300000000
console.log(jsonParse('-2.5e-2'));       // -0.025

// 对比原生JSON.parse
const testStr = '{"a":1,"b":[2,3],"c":{"d":4}}';
console.log('自定义:', jsonParse(testStr));
console.log('原生:', JSON.parse(testStr));

关键点

  • 三种实现方式:eval(不安全)、Function构造函数(相对安全)、手动解析(最安全且完整)

  • 递归下降解析:采用递归下降的方式解析JSON结构,每种数据类型对应一个解析函数

  • 词法分析:通过游标(索引)逐字符扫描,识别不同的token类型(对象、数组、字符串、数字等)

  • 空白字符处理:JSON允许在值之间存在空白字符(空格、换行、制表符),需要正确跳过

  • 字符串转义:正确处理转义字符(\n\t\"等)和Unicode转义(\uXXXX

  • 数字格式:支持整数、小数、负数和科学计数法(如1.23e10

  • 错误处理:在解析失败时抛出有意义的错误信息,指明错误位置

  • 边界情况:处理空对象{}、空数组[]、嵌套结构等特殊情况

  • 性能考虑:手动解析虽然代码较多,但避免了eval的安全风险,且性能可控