Skip to content

Latest commit

 

History

History
335 lines (238 loc) · 8.81 KB

22.implement-JSON-parse.md

File metadata and controls

335 lines (238 loc) · 8.81 KB

22. implement JSON.parse()

JSON.parse() is a JavaScript method used to parse a JSON string, constructing the JavaScript value or object described by the string.

Problem

https://bigfrontend.dev/problem/implement-JSON-parse

Problem Description

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.

Solution 1

/**
 * @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

/**
 * @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);
}

Usage

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 }]

Explanation

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:

  1. 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 using detectDataType(str), and then calls the appropriate function to parse the data.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

Real World Examples

Here are some real-world examples of its use:

1. Retrieving Data from Local Storage

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" } }

2. Receiving Data from a Server

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));

3. Parsing Configuration Files

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" }

4. Processing JSON Data from an API

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

5. Handling Dynamic Data

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

6. Parsing Nested JSON Data

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

7. Parsing JSON with Error Handling

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