https://bigfrontend.dev/problem/implement-JSON-parse
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.
/**
* @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;
}/**
* @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);
}