Implementing Custom `JSON.stringify` in JavaScript



Implementing a custom version of JSON.stringify is a complex task that requires handling various data types, circular references, and optional arguments like replacer and space. In this episode, we'll delve into creating a simplified custom version of JSON.stringify.


What is JSON.stringify?

JSON.stringify converts a JavaScript object or value to a JSON string. This function is crucial for serializing data to be sent over a network or saved in a text-based format.

Real Interview Insights

Interviewers might ask you to:

  • Implement a custom JSON.stringify function.
  • Handle various data types, including objects, arrays, and primitives.
  • Address edge cases like circular references and replacer functions.

Implementing Custom JSON.stringify

Here's a basic implementation of a custom JSON.stringify function:

function customStringify(value, replacer, space) {
  const seen = new WeakSet();
 
  function stringify(val) {
    if (val === null) {
      return 'null';
    }
 
    if (typeof val === 'string') {
      return `"${val}"`;
    }
 
    if (typeof val === 'number' || typeof val === 'boolean') {
      return String(val);
    }
 
    if (Array.isArray(val)) {
      return `[${val.map(stringify).join(',')}]`;
    }
 
    if (typeof val === 'object') {
      if (seen.has(val)) {
        throw new TypeError('Converting circular structure to JSON');
      }
 
      seen.add(val);
 
      const keys = Object.keys(val);
      const entries = keys.map(key => `"${key}":${stringify(val[key])}`).join(',');
 
      return `{${entries}}`;
    }
 
    return undefined;
  }
 
  const jsonString = stringify(value);
  return space ? jsonString.replace(/(?!\B\"\S+?\":)\s/g, space) : jsonString;
}
Explanation:
  • Primitive Value Handling: Convert null, string, number, and boolean values to their JSON string equivalents.
  • Array Handling: Recursively stringify each element in the array.
  • Object Handling: Recursively stringify each key-value pair, throwing an error for circular references.
  • WeakSet: Use a WeakSet to track seen objects and detect circular references.

Practical Example

Consider an example with nested objects and arrays:

const obj = {
  name: "John",
  age: 30,
  hobbies: ["reading", "gaming"],
  address: {
    city: "New York",
    zip: 10001
  }
};
 
console.log(customStringify(obj)); 
// Output: {"name":"John","age":30,"hobbies":["reading","gaming"],"address":{"city":"New York","zip":10001}}

Handling Edge Cases

  1. Circular References: Detect and throw an error for circular references.
  2. replacer Function: Optionally transform values before stringifying.
  3. space Argument: Optionally add indentation for readability.

Enhanced Implementation with replacer and space Arguments

function customStringify(value, replacer, space) {
  const seen = new WeakSet();
 
  function stringify(val, key) {
    if (replacer) {
      val = replacer(key, val);
    }
 
    if (val === null) {
      return 'null';
    }
 
    if (typeof val === 'string') {
      return `"${val}"`;
    }
 
    if (typeof val === 'number' || typeof val === 'boolean') {
      return String(val);
    }
 
    if (Array.isArray(val)) {
      return `[${val.map((v, i) => stringify(v, i)).join(',')}]`;
    }
 
    if (typeof val === 'object') {
      if (seen.has(val)) {
        throw new TypeError('Converting circular structure to JSON');
      }
 
      seen.add(val);
 
      const keys = Object.keys(val);
      const entries = keys.map(key => `"${key}":${stringify(val[key], key)}`).join(',');
 
      return `{${entries}}`;
    }
 
    return undefined;
  }
 
  const jsonString = stringify(value, '');
  if (space) {
    return JSON.stringify(JSON.parse(jsonString), null, space);
  }
  return jsonString;
}
 
// Example usage with replacer and space
const obj = {
  name: "John",
  age: 30,
  hobbies: ["reading", "gaming"],
  address: {
    city: "New York",
    zip: 10001
  }
};
 
const replacer = (key, value) => (typeof value === 'number' ? undefined : value);
 
console.log(customStringify(obj, replacer, 2));
// Output: {
//   "name": "John",
//   "hobbies": [
//     "reading",
//     "gaming"
//   ],
//   "address": {
//     "city": "New York",
//     "zip": 10001
//   }
// }

In this enhanced version:

  • Replacer Function: A function to transform values before stringifying.
  • Space Argument: Adds indentation to the JSON string for better readability.

Use Cases for Custom JSON.stringify

  1. Custom Serialization: Tailoring the serialization process to fit specific needs.
  2. Debugging: Providing better control over how objects are converted to strings for debugging purposes.
  3. Performance Optimization: Optimizing the stringification process for specific scenarios.