Skip to content

Latest commit

 

History

History
164 lines (121 loc) · 3.39 KB

File metadata and controls

164 lines (121 loc) · 3.39 KB

22. implement JSON.parse()

Problem

https://bigfrontend.dev/problem/implement-JSON-parse

Problem Description

This is a follow-up on 21. implement JSON.stringify().

Believe you are already familiar with JSON.parse(), could you implement your own version?

In case you are not sure about the spec, MDN here might help.

JSON.parse() support a second parameter reviver, you can ignore that.

Solution 1

/**
 * @param {string} str
 * @return {object | Array | string | number | boolean | null}
 */
function parse(str) {
  const parsed = eval('(' + str + ')');
  if (str !== JSON.stringify(parsed)) {
    throw new Error();
  }

  return parsed;
}

Solution 2

/**
 * @param {string} str
 * @return {object | Array | string | number | boolean | null}
 */
function parse(str) {
  const dataType = detectDataType(str);

  if (dataType === 'object') return parseObj(str);

  if (dataType === 'array') return parseArr(str);

  return parsePrimitive(str);
}

function parseObj(str) {
  const obj = {};

  str = str.slice(1, -1);
  if (str.endsWith(':')) throw new Error();

  while (str.length > 0) {
    str = skipLeadingSpace(str);

    const regex = /^"(.+?)"\s?:/;
    const matchedKey = str.match(regex);
    if (!matchedKey) throw new Error();

    const key = matchedKey[1];

    let rest = str.slice(matchedKey[0].length);
    rest = skipLeadingSpace(rest);

    let matchedVal;
    let val;
    if ((matchedVal = rest.match(/^({.+})\s?,?/))) {
      val = matchedVal[1];
      obj[key] = parseObj(val);
    } else if ((matchedVal = rest.match(/^(\[.+\])\s?,?/))) {
      val = matchedVal[1];
      obj[key] = parseArr(val);
    } else if ((matchedVal = rest.match(/^(\w+)\s?,?/u))) {
      val = matchedVal[1];
      obj[key] = parsePrimitive(val);
    } else if (
      (matchedVal = rest.match(/^("[\p{Emoji}\p{Alpha}]+.?")\s?,?/u))
    ) {
      val = matchedVal[1];
      obj[key] = parsePrimitive(val);
    } else {
      throw new Error();
    }

    str = rest.slice(matchedVal[0].length);
  }

  return obj;
}

function parseArr(str) {
  const arr = [];

  str = str.slice(1, -1);
  if (str.endsWith(',')) throw new Error();

  while (str.length > 0) {
    let item = str.match(/.+?,(?!"\w+":)/);
    if (!item) {
      item = str;
    } else {
      item = item[0].slice(0, -1);
    }
    const dataType = detectDataType(item);

    switch (dataType) {
      case 'object':
        arr.push(parseObj(item));
        break;
      case 'array':
        arr.push(parseArr(item));
        break;
      default:
        arr.push(parsePrimitive(item));
    }

    str = str.slice(item.length + 1);
  }

  return arr;
}

function parsePrimitive(str) {
  if (str.startsWith('"')) return str.slice(1, -1);

  if (!isNaN(str)) return Number(str);

  if (str === 'true') return true;

  if (str === 'false') return false;

  if (str === 'undefined') return undefined;

  return null;
}

function detectDataType(str) {
  if (str.startsWith('{') && str.endsWith('}')) {
    return 'object';
  }

  if (str.startsWith('[') && str.endsWith(']')) {
    return 'array';
  }

  return 'primitive';
}

function skipLeadingSpace(str) {
  const firstNonSpaceCharIdx = str.search(/\S/);
  if (firstNonSpaceCharIdx === -1) return '';
  return str.slice(firstNonSpaceCharIdx);
}