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.
- Remove all the spaces from the string.
- If the string is empty or has invalid characters like starting with single quotes, then throw an error.
- If the string has only
[]
,{}
,true
,false
, or number, convert and return the respective values. - 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{}
). - 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('[]')); /* [] */