-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: adding more details for JSON.stringify()
- Loading branch information
1 parent
fec1f2d
commit b103afc
Showing
2 changed files
with
181 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
``` |