From fec1f2d3e60a86a0be0b1053ef2954bf74f5bd35 Mon Sep 17 00:00:00 2001 From: Shaban-Eissa Date: Sat, 1 Jun 2024 18:29:49 +0300 Subject: [PATCH] feat: implementation for JSON.stringify() --- 21.implement-JSON-stringify.md | 338 +++++++++++++++++++++++++++++++++ 1 file changed, 338 insertions(+) create mode 100644 21.implement-JSON-stringify.md diff --git a/21.implement-JSON-stringify.md b/21.implement-JSON-stringify.md new file mode 100644 index 0000000..a72913a --- /dev/null +++ b/21.implement-JSON-stringify.md @@ -0,0 +1,338 @@ +# 21. implement JSON.stringify() +JSON.stringify() is a JavaScript method used to convert a JavaScript object or value to a JSON string. + +### Problem + +https://bigfrontend.dev/problem/implement-JSON-stringify + +# + +### Problem Description + +I believe you've used `JSON.stringify()` before, do you know the details of how it handles arbitrary data? + +Please have a guess on the details and then take a look at the [explanation on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify), it is actually pretty complex. + +In this problem, you are asked to implement your own version of `JSON.stringify()`. + +In a real interview, you are not expected to cover all the cases, just decide the scope with interviewer. But for a goal of practicing, your code here will be tested against a lot of data types. Please try to cover as much as you can. + +Attention to the circular reference. + +**note** + +`JSON.stringify()` support two more parameters which is not required here. + +# + +### Solution + +```js +/** + * @param {any} data + * @return {string} + */ +function stringify(data) { + const typeOfData = detectDataType(data); + + if (typeOfData === 'array') { + return stringifyArr(data); + } + + if (typeOfData === 'object' || typeOfData === 'map') { + return stringifyObj(data); + } + + return _stringify(typeOfData, data); +} + +function stringifyObj(data) { + let stringifiedData = []; + + for (const key of Object.keys(data)) { + const val = data[key]; + const typeOfVal = detectDataType(val); + + if ( + typeOfVal === 'symbol' || + typeOfVal === 'function' || + typeOfVal === 'undefined' + ) { + continue; + } + + let stringifiedKey = `\"${key}\":`; + + switch (typeOfVal) { + case 'array': + stringifiedKey += stringifyArr(val); + break; + case 'object': + case 'map': + stringifiedKey += stringifyObj(val); + break; + default: + stringifiedKey += _stringify(typeOfVal, val); + } + + stringifiedData.push(stringifiedKey); + } + + return `{${stringifiedData.join(',')}}`; +} + +function stringifyArr(data) { + let stringifiedData = []; + + for (const [index, val] of data.entries()) { + if (isNaN(index)) { + continue; + } + + const typeOfVal = detectDataType(val); + + switch (typeOfVal) { + case 'array': + stringifiedData.push(stringifyArr(val)); + break; + case 'object': + case 'map': + stringifiedData.push(stringifyObj(val)); + break; + default: + stringifiedData.push(_stringify(typeOfVal, val)); + } + } + + return `[${stringifiedData.join(',')}]`; +} + +function _stringify(typeOfData, data) { + switch (typeOfData) { + case 'string': + return `\"${data}\"`; + case 'number': + case 'boolean': + return String(data); + case 'function': + return undefined; + case 'date': + return `"${data.toISOString()}"`; + case 'set': + case 'map': + case 'weakSet': + case 'weakMap': + return '{}'; + case 'bigint': + throw new Error("TypeError: BigInt value can't be serialized in JSON"); + default: + return 'null'; + } +} + +const dataTypes = new Map([ + [Number, 'number'], + [String, 'string'], + [Boolean, 'boolean'], + [Array, 'array'], + [ArrayBuffer, 'arraybuffer'], + [Date, 'date'], + [Set, 'set'], + [Map, 'map'], + [WeakSet, 'weakSet'], + [WeakMap, 'weakMap'], +]); + +function detectDataType(data) { + if (typeof data === 'number' && isNaN(data)) { + return 'NaN'; + } + + if (data === Infinity) { + return 'infinity'; + } + + if (typeof data !== 'object') { + return typeof data; + } + + if (data === null) { + return 'null'; + } + + for (const [type, name] of dataTypes.entries()) { + if (data instanceof type) { + return name; + } + } + + return 'object'; +} +``` + +# + +### Usage + +```js +console.log(stringify({ x: 5, y: 6 })); // {"x":5,"y":6} +console.log(stringify([1, 2, 3])); // [1,2,3] +console.log(stringify("test")); // "test" +console.log(stringify(5)); // 5 +console.log(stringify(true)); // true +console.log(stringify(null)); // null +console.log(stringify(new Date())); // "2021-07-09T20:22:30.000Z" +console.log(stringify(new Set([1, 2, 3]))); // {} +console.log(stringify(new Map([["key", "value"]]))); // {} +console.log(stringify(new WeakSet())); // {} +console.log(stringify(new WeakMap())); // {} +console.log(stringify(new ArrayBuffer(10))); // {} +console.log(stringify(NaN)); // NaN +console.log(stringify(Infinity)); // infinity +console.log(stringify(BigInt(10))); // throws TypeError +console.log(stringify(Symbol("test"))); // {} +console.log(stringify(() => {})); // undefined +console.log(stringify(undefined)); // undefined +``` + + +# + +### Real World Examples + +Here are some real-world examples of its use: + +### 1. Storing Data in Local Storage + +When working with web applications, you might want to store complex data structures in the browser's local storage. Since local storage can only store strings, you need to use JSON.stringify() to convert objects to strings before storing them. + +**Example:** + +```javascript +const user = { + name: "Alice", + age: 25, + preferences: { + theme: "dark", + language: "en" + } +}; + +// Convert the user object to a JSON string +const userString = JSON.stringify(user); + +// Store the JSON string in local storage +localStorage.setItem('user', userString); + +// Later, you can retrieve it and parse it back to an object +const retrievedUserString = localStorage.getItem('user'); +const retrievedUser = JSON.parse(retrievedUserString); + +console.log(retrievedUser); +// Output: { name: "Alice", age: 25, preferences: { theme: "dark", language: "en" } } +``` + +### 2. Sending Data to a Server + +When sending data to a server via an HTTP request, you often need to send JSON. Using JSON.stringify(), you can convert your data into a JSON string before sending it. + +**Example:** + +```javascript +const product = { + id: 123, + name: "Laptop", + price: 999.99, + inStock: true +}; + +// Convert the product object to a JSON string +const productString = JSON.stringify(product); + +// Send the JSON string to the server using fetch +fetch('https://api.example.com/products', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: productString +}) +.then(response => response.json()) +.then(data => console.log(data)) +.catch(error => console.error('Error:', error)); +``` + +### 3. Logging Data for Debugging + +When debugging, you might want to log objects as strings for easier readability or to include them in error messages. + +**Example:** + +```javascript +const errorDetails = { + message: "Something went wrong", + code: 500, + timestamp: new Date() +}; + +// Convert the error details object to a JSON string for logging +console.error(JSON.stringify(errorDetails)); + +// Output: {"message":"Something went wrong","code":500,"timestamp":"2023-04-26T08:34:00.000Z"} +``` + +### 4. Deep Copying Objects + +While JSON.stringify() and JSON.parse() can be used for deep copying objects, it's important to note that this method has limitations, such as not supporting functions or special object types (e.g., Date, undefined). + +**Example:** + +```javascript +const original = { + name: "Original", + details: { + age: 30, + active: true + } +}; + +// Create a deep copy using JSON.stringify() and JSON.parse() +const copy = JSON.parse(JSON.stringify(original)); + +// Modify the copy +copy.details.age = 40; + +console.log(original.details.age); // Output: 30 +console.log(copy.details.age); // Output: 40 +``` + +### 5. Formatting Output with Indentation + +You can use JSON.stringify() to format JSON strings with indentation for better readability, useful for generating human-readable logs or documents. + +**Example:** + +```javascript +const data = { + id: 1, + name: "Test", + items: ["item1", "item2", "item3"] +}; + +// Convert the data object to a pretty-printed JSON string +const prettyDataString = JSON.stringify(data, null, 2); + +console.log(prettyDataString); + +// Output: +// { +// "id": 1, +// "name": "Test", +// "items": [ +// "item1", +// "item2", +// "item3" +// ] +// } +``` +