From b103afc6a5acaa04a67c5d1fc7d10a3542a20ce8 Mon Sep 17 00:00:00 2001 From: Shaban-Eissa Date: Sat, 1 Jun 2024 18:40:25 +0300 Subject: [PATCH] refactor: adding more details for JSON.stringify() --- 21.implement-JSON-stringify.md | 17 ++++ 22.implement-JSON-parse.md | 164 +++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 22.implement-JSON-parse.md diff --git a/21.implement-JSON-stringify.md b/21.implement-JSON-stringify.md index a72913a..b4c8a81 100644 --- a/21.implement-JSON-stringify.md +++ b/21.implement-JSON-stringify.md @@ -195,6 +195,23 @@ console.log(stringify(() => {})); // undefined console.log(stringify(undefined)); // undefined ``` +# + +### Explanation + +This code is a custom implementation of JSON.stringify() function in JavaScript. It converts JavaScript values into a JSON string. Here's a breakdown of the main functions: + +1. `stringify(data)`: This is the main function that takes a JavaScript value and returns a JSON string. It first detects the type of the data using `detectDataType(data)`, and then calls the appropriate function to stringify the data. + +2. `stringifyObj(data)`: This function stringifies a JavaScript object. It iterates over the keys of the object, detects the type of each value, and then stringifies the value. It ignores functions, symbols, and undefined values. The resulting key-value pairs are joined into a string with comma separators and enclosed in curly braces. + +3. `stringifyArr(data)`: This function stringifies a JavaScript array. It iterates over the elements of the array, detects the type of each element, and then stringifies the element. The resulting elements are joined into a string with comma separators and enclosed in square brackets. + +4. `_stringify(typeOfData, data)`: This function stringifies a JavaScript value based on its type. It handles string, number, boolean, date, set, map, weakSet, weakMap, and bigint types. It throws an error for bigint values because they can't be serialized in JSON. + +5. `detectDataType(data)`: This function detects the type of a JavaScript value. It uses a map of constructors to type names for complex types, and the `typeof` operator for simple types. It also handles special cases like NaN and Infinity. + + # diff --git a/22.implement-JSON-parse.md b/22.implement-JSON-parse.md new file mode 100644 index 0000000..eecba4a --- /dev/null +++ b/22.implement-JSON-parse.md @@ -0,0 +1,164 @@ +# 22. implement JSON.parse() + +### Problem + +https://bigfrontend.dev/problem/implement-JSON-parse + +# + +### Problem Description + +This is a follow-up on [21. implement JSON.stringify()](https://bigfrontend.dev/problem/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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) might help. + +`JSON.parse()` support a second parameter `reviver`, you can ignore that. + +# + +### Solution 1 + +```js +/** + * @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 + +```js +/** + * @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); +} +```