JSON.parse() is a JavaScript method used to parse a JSON string, constructing the JavaScript value or object described by the string.
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);
}
console.log(parse("1")); // 1
console.log(parse("true")); // true
console.log(parse("null")); // null
console.log(parse('"abc"')); // abc
console.log(parse("false")); // false
console.log(parse("undefined")); // undefined
console.log(parse('{"a":1,"b":2}')); // { a: 1, b: 2 }
console.log(parse('["a", "b", "c"]')); // [ 'a', 'b', 'c' ]
console.log(parse(['{"a":1,"b":2}', '{"a":1,"b":2}'])); // [{ a: 1, b: 2 }, { a: 1, b: 2 }]
This code is a custom implementation of JSON.parse() function in JavaScript. It converts a JSON string into a JavaScript value. Here's a breakdown of the main functions:
-
parse(str)
: This is the main function that takes a JSON string and returns a JavaScript value. It first detects the type of the data usingdetectDataType(str)
, and then calls the appropriate function to parse the data. -
parseObj(str)
: This function parses a JSON object string into a JavaScript object. It iterates over the string, extracting keys and values, and then parses the values based on their detected data type. It throws an error if the string is not properly formatted. -
parseArr(str)
: This function parses a JSON array string into a JavaScript array. It iterates over the string, extracting items, and then parses the items based on their detected data type. It throws an error if the string is not properly formatted. -
parsePrimitive(str)
: This function parses a JSON primitive string (number, string, boolean, null, or undefined) into a JavaScript primitive value. It handles each type of primitive separately. -
detectDataType(str)
: This function detects the type of a JSON string. It checks the first and last characters of the string to determine if it's an object, array, or primitive. -
skipLeadingSpace(str)
: This function removes leading whitespace from a string. It finds the index of the first non-space character and slices the string from that index.
Here are some real-world examples of its use:
When you store data in local storage as a JSON string, you need to parse it back into a JavaScript object when retrieving it.
Example:
// Retrieve the JSON string from local storage
const userString = localStorage.getItem('user');
// Parse the JSON string to a JavaScript object
const user = JSON.parse(userString);
console.log(user);
// Output: { name: "Alice", age: 25, preferences: { theme: "dark", language: "en" } }
When you receive JSON data from a server, you need to parse it to use it as a JavaScript object.
Example:
fetch('https://api.example.com/products/123')
.then(response => response.json()) // .json() automatically parses JSON
.then(data => {
// data is already parsed as a JavaScript object
console.log(data);
})
.catch(error => console.error('Error:', error));
If your application reads configuration data from a JSON file, you need to parse it into a JavaScript object.
Example:
const configString = `
{
"apiKey": "12345",
"theme": "dark",
"language": "en"
}`;
// Parse the JSON string to a JavaScript object
const config = JSON.parse(configString);
console.log(config);
// Output: { apiKey: "12345", theme: "dark", language: "en" }
When you interact with an API that returns JSON data, you need to parse the response to manipulate the data.
Example:
const jsonResponse = '{"name": "Bob", "age": 30, "isAdmin": false}';
// Parse the JSON string to a JavaScript object
const user = JSON.parse(jsonResponse);
console.log(user.name); // Output: Bob
console.log(user.age); // Output: 30
console.log(user.isAdmin); // Output: false
When dealing with dynamic data sources such as user input or third-party services that provide JSON, you need to parse the input to work with it.
Example:
const userInput = '{"searchTerm": "JavaScript", "resultsPerPage": 10}';
// Parse the JSON string to a JavaScript object
const searchParameters = JSON.parse(userInput);
console.log(searchParameters.searchTerm); // Output: JavaScript
console.log(searchParameters.resultsPerPage); // Output: 10
When working with nested JSON data structures, JSON.parse() is essential for converting the string to an object.
Example:
const nestedJsonString = `
{
"name": "John",
"address": {
"street": "123 Main St",
"city": "New York",
"zip": "10001"
},
"phones": ["555-1234", "555-5678"]
}`;
// Parse the JSON string to a JavaScript object
const person = JSON.parse(nestedJsonString);
console.log(person.address.city); // Output: New York
console.log(person.phones[1]); // Output: 555-5678
When parsing JSON, it is good practice to handle potential errors, especially with user input or data from unreliable sources.
Example:
const invalidJsonString = '{"name": "John", "age": 30'; // Missing closing bracket
try {
const user = JSON.parse(invalidJsonString);
console.log(user);
} catch (error) {
console.error('Invalid JSON:', error.message);
}
// Output: Invalid JSON: Unexpected end of JSON input