Implement JSON parse in JavaScript

Implement a simple polyfill for JSON.parse() in javaScript.

Example

const json = '{"result":true, "count":42}';
const obj = JSON.parse(json);

console.log(obj);
// expected output: {"result": true, "count": 42}

JSON.parse() method is exactly opposite of the JSON.stringify(). It takes a string as input and parses it to javascript value if possible, else throws an error.

This is can be implemented in a step-by-step manner.

  1. Remove all the spaces from the string.
  2. If the string is empty or has invalid characters like starting with single quotes, then throw an error.
  3. If the string has only [], {}, true, false, or number, convert and return the respective values.
  4. The last part is to handle the nested values like arrays, objects, and arrays of objects. For this, we will first get their individual values on comma separation by removing the braces (without [] and {}).
  5. Then if it is an array, parse each value by calling the same function recursively and return as an array, else if it is an object, split the values on : and parse the key as well as the value both and return them as object.
static parse(string) {
    // remove the space from the string
    string = string.replace(/ /g, '');
    
    //convert each value accordingly
    switch(string) {
      case '':
        throw new Error();
      case 'null':
        return null;
      case '{}':
        return {};
      case '[]':
        return [];
      case 'true':
        return true;
      case 'false':
        return false;
      default:
        // if number return as number
        if (+string === +string) {
          return Number(string);
        }
        // if escaped single quotes, throw error
        else if (string[0] === '\'') {
          throw new Error();
        }
        // if escaped double quotes, throw error
        else if (string[0] === '\"') {
          // same as string.substr(1, string.length-2);
          return string.slice(1, -1);
        } else { 
          // if [] || {}
          // get the inner string
          const innerString = string.slice(1, -1);

          // get the values from the string
          // array of pairs if {}
          // array of single values if []
          const subStrings = this.stringSplitByComma(innerString);

          // if it is array
          if (string[0] === '[') {
            // parse each value
            return subStrings.map(item => this.parse(item));
          } else if (string[0] === '{') {

            // if it object
            // get the key and value by splitting on :
            // parse the key and value individually
            return subStrings.reduce((acc, item) => {
              if (item.indexOf(':') > -1) {
                const index = item.indexOf(':');
                const thisKey = item.substring(0, index);
                const thisValue = item.substring(index + 1);
                acc[this.parse(thisKey)] = this.parse(thisValue);
              }

              return acc;
            }, {});
          }   
        }
      }
    }

To get the comma-separated values from the array or the object, we will be using the sliding window technique.

  • Track the opening and closing parentheses [] as well as the curly braces {}.
  • Whenever a comma is spotted while traversing the string, get the value between the opening and closing parentheses and/or curly braces and push it into an array so that each of these values can be parsed individually.
  // helper function
  // to get the comma separated values of array or objects
  static stringSplitByComma(string) {
    const allStrs = [];
    // lParen tracks the parentheses []
    // lCurly tracks the curly braces {}
    let lParen = 0, lCurly = 0;
    let left = 0, right = 0;
    
    // traverse the string
    // whenever a comma is spotted
    // store the value
    while (right <= string.length) {
      const rChar = string[right];
      
      // track the index for the content
      // inside the array [] or object {}
      if (rChar === '[') lParen++;
      if (rChar === '{') lCurly++;
      if (rChar === ']') lParen--;
      if (rChar === '}') lCurly--;
      
      // if a comma is spotted
      if ((rChar === ',' && lParen === 0 && lCurly === 0) ||
        right === string.length) 
      {
        // get the value inbetween and store it
        const thisStr = string.substring(left, right);
        allStrs.push(thisStr);
        left = right + 1;
      }
      
      right++;
    }
    
    return allStrs;
  }

Complete code

class JSON{
  static parse(string) {
    // remove the space from the string
    string = string.replace(/ /g, '');
    
    // convert each value accordingly
    switch(string) {
      case '':
        throw new Error();
      case 'null':
        return null;
      case '{}':
        return {};
      case '[]':
        return [];
      case 'true':
        return true;
      case 'false':
        return false;
      default:
        // if number return as number
        if (+string === +string) {
          return Number(string);
        }
        // if escaped single quotes, throw error
        else if (string[0] === '\'') {
          throw new Error();
        }
        // if escaped double quotes, throw error
        else if (string[0] === '\"') {
          // same as string.substr(1, string.length-2);
          return string.slice(1, -1);
        } else { 
          // if [] || {}
          // get the inner string
          const innerString = string.slice(1, -1);

          // get the values from the string
          // array of pairs if {}
          // array of single values if []
          const subStrings = this.stringSplitByComma(innerString);

          // if it is array
          if (string[0] === '[') {
            // parse each value
            return subStrings.map(item => this.parse(item));
          } else if (string[0] === '{') {

            // if it object
            // get the key and value by splitting on :
            // parse the key and value individually
            return subStrings.reduce((acc, item) => {
              if (item.indexOf(':') > -1) {
                const index = item.indexOf(':');
                const thisKey = item.substring(0, index);
                const thisValue = item.substring(index + 1);
                acc[this.parse(thisKey)] = this.parse(thisValue);
              }

              return acc;
            }, {});
          }   
        }
      }
    }

  // helper function
  // to get the comma separated values of array or objects
  static stringSplitByComma(string) {
    const allStrs = [];
    // lParen tracks the parentheses []
    // lCurly tracks the curly braces {}
    let lParen = 0, lCurly = 0;
    let left = 0, right = 0;
    
    // traverse the string
    // whenever a comma is spotted
    // store the value
    while (right <= string.length) {
      const rChar = string[right];
      
      // track the index for the content
      // inside the array [] or object {}
      if (rChar === '[') lParen++;
      if (rChar === '{') lCurly++;
      if (rChar === ']') lParen--;
      if (rChar === '}') lCurly--;
      
      // if a comma is spotted
      if ((rChar === ',' && lParen === 0 && lCurly === 0) ||
        right === string.length) 
      {
        // get the value inbetween and store it
        const thisStr = string.substring(left, right);
        allStrs.push(thisStr);
        left = right + 1;
      }
      
      right++;
    }
    
    return allStrs;
  }
}
console.log(JSON.parse('[{"result":true,"count":42}]'));
/*
[
  {
    "result": true,
    "count": 42
  }
]
*/

console.log(JSON.parse('{"next": {"next": {"val": 30},"val": 20},"val": 10 }'));
/*
{
  "next": {
    "next": {
      "val": 30
    },
    "val": 20
  },
  "val": 10
}
*/

console.log(JSON.parse('{"a": 1,"b": {"c": 2,"d": -3,"e": {"f": {"g": -4}},"h": {"i": 5,"j": 6}}}'));
/*
{
  "a": 1,
  "b": {
    "c": 2,
    "d": -3,
    "e": {
      "f": {
        "g": -4
      }
    },
    "h": {
      "i": 5,
      "j": 6
    }
  }
}
*/

console.log(JSON.parse('{"a": 1,"b": {"c": "Hello World","d": 2,"e": {"f": {"g": -4}},"h": "Good Night Moon"}}'));
/*
{
  "a": 1,
  "b": {
    "c": "HelloWorld",
    "d": 2,
    "e": {
      "f": {
        "g": -4
      }
    },
    "h": "GoodNightMoon"
  }
}
*/

console.log(JSON.parse('[{"x": 5,"y": 6}]'));
/*
[
  {
    "x": 5,
    "y": 6
  }
]
*/

console.log(JSON.parse('[3,"false",false,null]'));
/*
[
  3,
  "false",
  false,
  null
]
*/

console.log(JSON.parse('{"x": [10,null,null,null]}'));
/*
{
  "x": [
    10,
    null,
    null,
    null
  ]
}
*/

console.log(JSON.parse('[]'));
/*
[]
*/