Akiq2016/blog

writting a simple JSON parser in js

Opened this issue · 0 comments

// https://www.json.org/json-en.html
const err = () => {
  throw new Error('Not valid JSON');
}

/**
 * 1. item splitted by comma
 * 2. item type are 'string/array/object' and others 
 *  I. first type, need to find out the right startIndex and endIndex
 *  II. second type, basic type without complex string
 * @param {string} data
 */
const _parseArray = (data) => {
  const stack = [];
  const comma = ',';
  const token = {
    '{': '}',
    '[': ']',
    '"': '"',
  }
  const itemString = data.slice(1, -1);
  let startIndex = 0;

  if (itemString.length < 1) return [];

  const res = [];
  console.log('itemString:', itemString);
  for (let i = 0, l = itemString.length; i <= l; i++) {
    let checkStack = true;

    if (stack[stack.length - 1] === '\\') { // handle \" in string
      stack.pop();
      if (itemString[i] === `"`) {
        checkStack = false;
      }
    } else if (itemString[i] === '\\') {
      stack.push('\\');
      checkStack = false;
    } else if (stack[stack.length - 1] === '"') { // handle the special token in string "xxx"
      if (itemString[i] !== `"`) {
        checkStack = false;
      }
    }

    // use stack to record special token pair
    if (checkStack) {
      if (itemString[i] === stack[stack.length - 1]) {
        stack.pop()
        console.log('-', token[itemString[i]]);
      } else if (token[itemString[i]]) {
        stack.push(token[itemString[i]])
        console.log('++', itemString[i], i, stack);
      }
    }

    if (!stack.length && itemString[i] === comma || i === itemString.length) {
      console.log('==', i, itemString.length, itemString[i], itemString.slice(startIndex, i))
      res.push(parseJSON(itemString.slice(startIndex, i)));
      startIndex = i + 1;
    }
  }
  return res;
}

/**
 * key:  string
 * value:  any type
 * @param {string} data
 */
const _parseObject = (data) => {
  const stack = [];
  let key = null;
  let startIndex = 0;
  const colon = ':';
  const comma = ','
  const keyToken = '"';
  const token = {
    '{': '}',
    '[': ']',
    '"': '"',
  }

  const itemString = data.slice(1, -1);
  const res = {};
  for(let i = 0, l = itemString.length; i <= l; i++) {
    // <string:>
    if (key === null) {
      if (stack.length === 0 && itemString[i] === keyToken) {
        stack.push(keyToken);
        startIndex = i;
      } else if (stack[stack.length - 1] === '\\') {
        stack.pop();
      } else if (itemString[i] === '\\') {
        stack.push('\\');
      } else if (itemString[i] === keyToken) {
        stack.pop();
      }

      if (stack.length === 0 && itemString[i] === colon) {
        key = parseJSON(itemString.slice(startIndex, i));
        startIndex = i + 1;
        console.log('key:', key);
      }
    } else {
      // this condition mostly reuse the parseArray's code
      let checkStack = true;

      if (stack[stack.length - 1] === '\\') {
        stack.pop();
        if (itemString[i] === `"`) {
          checkStack = false;
        }
      } else if (itemString[i] === '\\') {
        stack.push('\\');
        checkStack = false;
      } else if (stack[stack.length - 1] === '"') {
        if (itemString[i] !== `"`) {
          checkStack = false;
        }
      }

      if (checkStack) {
        if (itemString[i] === stack[stack.length - 1]) {
          stack.pop()
        } else if (token[itemString[i]]) {
          stack.push(token[itemString[i]])
        }
      }

      if (!stack.length && itemString[i] === comma || i === itemString.length) {
        console.log('value:', itemString.slice(startIndex, i));
        res[key] = parseJSON(itemString.slice(startIndex, i))
        startIndex = i + 1;
        key = null;
        console.log('res:', res);
        console.log('========')
      }
    }
  }

  return res;
}

/**
 *
 * @param {string} str
 */
function parseJSON(str) {
  if (str.startsWith('"')) { // string
    return str.slice(1, -1);
  } else if (str === 'true') { // true
    return true;
  } else if (str === 'false') { // false
    return false;
  } else if (str === 'null') { // null
    return null;
  } else if (!Number.isNaN(+str)) { // number
    return +str;
  } else if (str.startsWith('[')) { // array
    return _parseArray(str);
  } else if (str.startsWith('{')) { // object
    return _parseObject(str);
  } else {
    err();
  }
}

// console.log(parseJSON(JSON.stringify([null,2,true,[4],5])))
// console.log(parseJSON(JSON.stringify([4, 8, ["1[\"", 3, 4]])))
console.log(parseJSON(JSON.stringify({ 'a\,\"': [1, [1]], b: 2 })))