Facebook Pixel

2633. Convert Object to JSON String 🔒

Problem Description

This problem asks you to implement a function that converts JavaScript values into their JSON string representation without using the built-in JSON.stringify method.

The function should handle the following data types:

  • null: Should return the string "null"
  • string: Should be wrapped in double quotes, e.g., "hello" becomes "\"hello\""
  • number: Should be converted to its string representation, e.g., 42 becomes "42"
  • boolean: Should return "true" or "false"
  • array: Should return elements wrapped in square brackets separated by commas, e.g., [1, 2, 3] becomes "[1,2,3]"
  • object: Should return key-value pairs wrapped in curly braces, with keys as strings and separated by commas, e.g., {a: 1, b: 2} becomes "{"a":1,"b":2}"

Key requirements:

  1. The output string should not contain extra spaces
  2. For objects, the order of keys in the output should match the order returned by Object.keys()
  3. The function should recursively handle nested structures (arrays within objects, objects within arrays, etc.)

The solution uses a recursive approach where:

  • Base cases handle primitives (null, strings, numbers, booleans)
  • For arrays, it maps each element through the same function recursively and joins them with commas
  • For objects, it uses Object.entries() to get key-value pairs, converts both keys and values to JSON strings recursively, and formats them as "key":value pairs joined by commas
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that JSON stringification is inherently a recursive process - we need to handle each data type according to its JSON representation rules, and for composite types (arrays and objects), we need to recursively process their contents.

We can think of this problem as a tree traversal where:

  • Leaf nodes are primitive values (null, strings, numbers, booleans)
  • Internal nodes are containers (arrays and objects) that hold other values

Starting from the simplest cases, we handle each JavaScript type:

  1. Primitives first: These are our base cases that don't require recursion. null becomes "null", strings get wrapped in quotes, numbers and booleans just need .toString()

  2. Arrays: Once we know how to stringify individual elements, an array is just those stringified elements joined with commas and wrapped in brackets. The pattern [element1,element2,...] naturally emerges.

  3. Objects: Similar to arrays, but we need to handle key-value pairs. Since JSON requires keys to be strings, we stringify the key, add a colon, then stringify the value. The pattern {"key1":value1,"key2":value2,...} follows naturally.

The recursive nature becomes clear when we realize that arrays can contain objects, objects can contain arrays, and both can be nested to any depth. By having our function call itself for each element or value, we automatically handle any level of nesting.

Using Object.entries() for objects is a natural choice because it gives us key-value pairs in the correct order, and we can easily transform each pair into the required "key":value format. Similarly, Array.map() is perfect for arrays since we need to transform each element individually before joining them together.

Solution Approach

The implementation uses a recursive function that performs type checking and applies the appropriate conversion for each data type:

1. Handle null case:

if (object === null) {
    return 'null';
}

Since null is technically an object in JavaScript but has special JSON representation, we check it first.

2. Handle string values:

if (typeof object === 'string') {
    return `"${object}"`;
}

Strings are wrapped in double quotes using template literals.

3. Handle numbers and booleans:

if (typeof object === 'number' || typeof object === 'boolean') {
    return object.toString();
}

These primitives are converted directly to strings using .toString().

4. Handle arrays:

if (Array.isArray(object)) {
    return `[${object.map(jsonStringify).join(',')}]`;
}
  • Use Array.isArray() to identify arrays (important since arrays are objects in JavaScript)
  • Map each element through jsonStringify recursively
  • Join the stringified elements with commas
  • Wrap the result in square brackets

5. Handle objects:

if (typeof object === 'object') {
    return `{${Object.entries(object)
        .map(([key, value]) => `${jsonStringify(key)}:${jsonStringify(value)}`)
        .join(',')}}`;
}
  • Use Object.entries() to get an array of [key, value] pairs
  • For each pair, stringify both the key and value recursively
  • Format as "key":value (note the key is stringified to ensure it's wrapped in quotes)
  • Join all pairs with commas
  • Wrap the result in curly braces

6. Default case:

return '';

Returns an empty string for any unexpected types (though in practice, the above cases should cover all valid JSON types).

The recursion naturally handles nested structures - when processing an array containing objects, the function calls itself on each object, which in turn may call itself on nested values, creating a depth-first traversal of the data structure.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through converting the object {name: "Alice", scores: [95, 87], active: true} to its JSON string representation.

Step 1: Start with the root object

  • Input: {name: "Alice", scores: [95, 87], active: true}
  • Type check: It's an object (not null, not an array)
  • Use Object.entries() to get: [["name", "Alice"], ["scores", [95, 87]], ["active", true]]

Step 2: Process first key-value pair ["name", "Alice"]

  • Stringify key "name":
    • Type: string → wrap in quotes → "\"name\""
  • Stringify value "Alice":
    • Type: string → wrap in quotes → "\"Alice\""
  • Combine: "\"name\":\"Alice\""

Step 3: Process second key-value pair ["scores", [95, 87]]

  • Stringify key "scores":
    • Type: string → wrap in quotes → "\"scores\""
  • Stringify value [95, 87]:
    • Type: array → recursively process each element
    • Element 95: Type number → .toString()"95"
    • Element 87: Type number → .toString()"87"
    • Join with comma and wrap in brackets → "[95,87]"
  • Combine: "\"scores\":[95,87]"

Step 4: Process third key-value pair ["active", true]

  • Stringify key "active":
    • Type: string → wrap in quotes → "\"active\""
  • Stringify value true:
    • Type: boolean → .toString()"true"
  • Combine: "\"active\":true"

Step 5: Combine all pairs

  • Join the three formatted pairs with commas:
    • "\"name\":\"Alice\",\"scores\":[95,87],\"active\":true"
  • Wrap in curly braces:
    • "{\"name\":\"Alice\",\"scores\":[95,87],\"active\":true}"

Final Result: "{\"name\":\"Alice\",\"scores\":[95,87],\"active\":true}"

The recursion depth in this example goes two levels deep: the root object contains an array, which contains numbers. Each level applies the appropriate formatting rules, and the recursive calls ensure that nested structures are properly converted regardless of their depth.

Solution Implementation

1def jsonStringify(object):
2    """
3    Converts a Python value to a JSON string representation
4  
5    Args:
6        object: The value to be converted to a JSON string
7  
8    Returns:
9        str: The JSON string representation of the input value
10    """
11    # Handle None value (equivalent to null in JavaScript)
12    if object is None:
13        return 'null'
14  
15    # Handle string type - wrap in double quotes
16    if isinstance(object, str):
17        return f'"{object}"'
18  
19    # Handle boolean type - convert to lowercase string ('true' or 'false')
20    if isinstance(object, bool):
21        return 'true' if object else 'false'
22  
23    # Handle number types (int and float) - convert directly to string
24    if isinstance(object, (int, float)):
25        return str(object)
26  
27    # Handle list type (array in JavaScript) - recursively stringify each element
28    if isinstance(object, list):
29        array_elements = [jsonStringify(element) for element in object]
30        return f"[{','.join(array_elements)}]"
31  
32    # Handle dictionary type (object in JavaScript) - recursively stringify each key-value pair
33    if isinstance(object, dict):
34        key_value_pairs = []
35        for key, value in object.items():
36            # Convert key to string and stringify both key and value
37            stringified_pair = f'{jsonStringify(str(key))}:{jsonStringify(value)}'
38            key_value_pairs.append(stringified_pair)
39        return f"{{{','.join(key_value_pairs)}}}"
40  
41    # Return empty string for unsupported types
42    return ''
43
1/**
2 * Converts a Java object to a JSON string representation
3 * @param object - The value to be converted to a JSON string
4 * @return The JSON string representation of the input value
5 */
6public class JsonStringifier {
7  
8    public static String jsonStringify(Object object) {
9        // Handle null value
10        if (object == null) {
11            return "null";
12        }
13      
14        // Handle string type - wrap in double quotes
15        if (object instanceof String) {
16            return "\"" + object + "\"";
17        }
18      
19        // Handle number types - convert directly to string
20        if (object instanceof Number) {
21            return object.toString();
22        }
23      
24        // Handle boolean type - convert directly to string
25        if (object instanceof Boolean) {
26            return object.toString();
27        }
28      
29        // Handle array/list type - recursively stringify each element
30        if (object instanceof List) {
31            List<?> list = (List<?>) object;
32            StringBuilder arrayBuilder = new StringBuilder();
33            arrayBuilder.append("[");
34          
35            // Process each element in the list
36            for (int i = 0; i < list.size(); i++) {
37                arrayBuilder.append(jsonStringify(list.get(i)));
38                // Add comma separator except for the last element
39                if (i < list.size() - 1) {
40                    arrayBuilder.append(",");
41                }
42            }
43          
44            arrayBuilder.append("]");
45            return arrayBuilder.toString();
46        }
47      
48        // Handle primitive array types
49        if (object.getClass().isArray()) {
50            // Convert array to list for easier processing
51            List<Object> arrayList = new ArrayList<>();
52          
53            // Check for different primitive array types
54            if (object instanceof int[]) {
55                for (int val : (int[]) object) {
56                    arrayList.add(val);
57                }
58            } else if (object instanceof double[]) {
59                for (double val : (double[]) object) {
60                    arrayList.add(val);
61                }
62            } else if (object instanceof boolean[]) {
63                for (boolean val : (boolean[]) object) {
64                    arrayList.add(val);
65                }
66            } else if (object instanceof Object[]) {
67                arrayList = Arrays.asList((Object[]) object);
68            }
69          
70            return jsonStringify(arrayList);
71        }
72      
73        // Handle map/object type - recursively stringify each key-value pair
74        if (object instanceof Map) {
75            Map<?, ?> map = (Map<?, ?>) object;
76            StringBuilder objectBuilder = new StringBuilder();
77            objectBuilder.append("{");
78          
79            int count = 0;
80            // Process each key-value pair in the map
81            for (Map.Entry<?, ?> entry : map.entrySet()) {
82                // Convert key to string and wrap in quotes
83                String key = jsonStringify(entry.getKey().toString());
84                // Recursively stringify the value
85                String value = jsonStringify(entry.getValue());
86              
87                objectBuilder.append(key).append(":").append(value);
88              
89                // Add comma separator except for the last entry
90                if (count < map.size() - 1) {
91                    objectBuilder.append(",");
92                }
93                count++;
94            }
95          
96            objectBuilder.append("}");
97            return objectBuilder.toString();
98        }
99      
100        // Return empty string for unsupported types
101        return "";
102    }
103}
104
1#include <string>
2#include <vector>
3#include <unordered_map>
4#include <variant>
5#include <memory>
6#include <sstream>
7
8// Define a recursive variant type to handle JSON values
9struct JsonValue;
10using JsonNull = std::nullptr_t;
11using JsonBool = bool;
12using JsonNumber = double;
13using JsonString = std::string;
14using JsonArray = std::vector<std::shared_ptr<JsonValue>>;
15using JsonObject = std::unordered_map<std::string, std::shared_ptr<JsonValue>>;
16
17struct JsonValue {
18    std::variant<JsonNull, JsonBool, JsonNumber, JsonString, JsonArray, JsonObject> value;
19  
20    // Constructors for different types
21    JsonValue() : value(nullptr) {}
22    JsonValue(std::nullptr_t) : value(nullptr) {}
23    JsonValue(bool b) : value(b) {}
24    JsonValue(double n) : value(n) {}
25    JsonValue(int n) : value(static_cast<double>(n)) {}
26    JsonValue(const std::string& s) : value(s) {}
27    JsonValue(const char* s) : value(std::string(s)) {}
28    JsonValue(const JsonArray& arr) : value(arr) {}
29    JsonValue(const JsonObject& obj) : value(obj) {}
30};
31
32/**
33 * Converts a JsonValue to a JSON string representation
34 * @param object - The JsonValue to be converted to a JSON string
35 * @return The JSON string representation of the input value
36 */
37std::string jsonStringify(const std::shared_ptr<JsonValue>& object) {
38    // Handle null pointer case
39    if (!object) {
40        return "null";
41    }
42  
43    // Use visitor pattern to handle different types
44    return std::visit([](const auto& val) -> std::string {
45        using T = std::decay_t<decltype(val)>;
46      
47        // Handle null value
48        if constexpr (std::is_same_v<T, JsonNull>) {
49            return "null";
50        }
51        // Handle boolean type - convert to "true" or "false"
52        else if constexpr (std::is_same_v<T, JsonBool>) {
53            return val ? "true" : "false";
54        }
55        // Handle number type - convert directly to string
56        else if constexpr (std::is_same_v<T, JsonNumber>) {
57            std::ostringstream oss;
58            oss << val;
59            return oss.str();
60        }
61        // Handle string type - wrap in double quotes
62        else if constexpr (std::is_same_v<T, JsonString>) {
63            return "\"" + val + "\"";
64        }
65        // Handle array type - recursively stringify each element
66        else if constexpr (std::is_same_v<T, JsonArray>) {
67            std::vector<std::string> array_elements;
68            for (const auto& element : val) {
69                array_elements.push_back(jsonStringify(element));
70            }
71          
72            // Join array elements with comma
73            std::string result = "[";
74            for (size_t i = 0; i < array_elements.size(); ++i) {
75                if (i > 0) {
76                    result += ",";
77                }
78                result += array_elements[i];
79            }
80            result += "]";
81            return result;
82        }
83        // Handle object type - recursively stringify each key-value pair
84        else if constexpr (std::is_same_v<T, JsonObject>) {
85            std::vector<std::string> key_value_pairs;
86            for (const auto& [key, value] : val) {
87                // Format as "key":value
88                std::string pair = "\"" + key + "\":" + jsonStringify(value);
89                key_value_pairs.push_back(pair);
90            }
91          
92            // Join key-value pairs with comma
93            std::string result = "{";
94            for (size_t i = 0; i < key_value_pairs.size(); ++i) {
95                if (i > 0) {
96                    result += ",";
97                }
98                result += key_value_pairs[i];
99            }
100            result += "}";
101            return result;
102        }
103      
104        // Return empty string for unsupported types (should not reach here)
105        return "";
106    }, object->value);
107}
108
1/**
2 * Converts a JavaScript value to a JSON string representation
3 * @param object - The value to be converted to a JSON string
4 * @returns The JSON string representation of the input value
5 */
6function jsonStringify(object: any): string {
7    // Handle null value
8    if (object === null) {
9        return 'null';
10    }
11  
12    // Handle string type - wrap in double quotes
13    if (typeof object === 'string') {
14        return `"${object}"`;
15    }
16  
17    // Handle number and boolean types - convert directly to string
18    if (typeof object === 'number' || typeof object === 'boolean') {
19        return object.toString();
20    }
21  
22    // Handle array type - recursively stringify each element
23    if (Array.isArray(object)) {
24        const arrayElements: string[] = object.map((element: any) => jsonStringify(element));
25        return `[${arrayElements.join(',')}]`;
26    }
27  
28    // Handle object type - recursively stringify each key-value pair
29    if (typeof object === 'object') {
30        const objectEntries: [string, any][] = Object.entries(object);
31        const keyValuePairs: string[] = objectEntries.map(([key, value]: [string, any]) => {
32            return `${jsonStringify(key)}:${jsonStringify(value)}`;
33        });
34        return `{${keyValuePairs.join(',')}}`;
35    }
36  
37    // Return empty string for undefined or other unsupported types
38    return '';
39}
40

Time and Space Complexity

Time Complexity: O(n) where n is the total number of primitive values (strings, numbers, booleans, null) in the entire object structure.

The analysis breaks down as follows:

  • For primitive types (null, string, number, boolean): O(1) operations
  • For arrays: O(m) where m is the array length, as we recursively process each element
  • For objects: O(k) where k is the number of key-value pairs, as we recursively process each entry
  • Each primitive value in the nested structure is visited exactly once
  • String concatenation and array joining operations are O(n) in total for all the characters/elements being joined

Space Complexity: O(d + n) where d is the maximum depth of the object/array nesting and n is the total size of the output string.

The space complexity consists of:

  • Call stack space: O(d) for the recursive calls, where d is the maximum depth of nested objects/arrays
  • Output string space: O(n) where n is the length of the final JSON string representation
  • Intermediate strings and arrays created during map() and join() operations: O(n) in total
  • The dominant factor is typically O(n) for the output string size, but in deeply nested structures with small data, O(d) could be significant

Common Pitfalls

1. Boolean Handling Order in Python

The most critical pitfall in the Python implementation is the order of type checking for booleans. In Python, bool is a subclass of int, so isinstance(True, int) returns True. This means if you check for integers before booleans, boolean values will be incorrectly converted to "1" or "0" instead of "true" or "false".

Incorrect Implementation:

# Wrong order - this will treat booleans as numbers
if isinstance(object, (int, float)):
    return str(object)
if isinstance(object, bool):
    return 'true' if object else 'false'

Correct Implementation:

# Check boolean BEFORE checking for numbers
if isinstance(object, bool):
    return 'true' if object else 'false'
if isinstance(object, (int, float)):
    return str(object)

2. String Escaping for Special Characters

The current implementation doesn't handle special characters in strings that need escaping in JSON, such as quotes, backslashes, newlines, tabs, etc.

Problem Example:

jsonStringify('Hello "World"')  # Returns: "Hello "World"" (invalid JSON)
jsonStringify('Line1\nLine2')   # Returns: "Line1
Line2" (invalid JSON)

Solution:

def escape_string(s):
    """Escape special characters in strings for JSON"""
    escape_dict = {
        '"': '\\"',
        '\\': '\\\\',
        '\n': '\\n',
        '\r': '\\r',
        '\t': '\\t',
        '\b': '\\b',
        '\f': '\\f'
    }
    result = ''
    for char in s:
        if char in escape_dict:
            result += escape_dict[char]
        elif ord(char) < 0x20:
            # Handle other control characters
            result += f'\\u{ord(char):04x}'
        else:
            result += char
    return result

# Then in jsonStringify:
if isinstance(object, str):
    return f'"{escape_string(object)}"'

3. Circular Reference Handling

The recursive implementation doesn't detect circular references, which will cause infinite recursion and a stack overflow.

Problem Example:

obj = {'a': 1}
obj['self'] = obj  # Circular reference
jsonStringify(obj)  # RecursionError: maximum recursion depth exceeded

Solution:

def jsonStringify(object, visited=None):
    if visited is None:
        visited = set()
  
    # Check for circular references in objects and lists
    if isinstance(object, (dict, list)):
        obj_id = id(object)
        if obj_id in visited:
            raise ValueError("Circular reference detected")
        visited.add(obj_id)
  
    # ... rest of the implementation ...
  
    # Remember to pass visited to recursive calls
    if isinstance(object, list):
        array_elements = [jsonStringify(element, visited.copy()) for element in object]
        return f"[{','.join(array_elements)}]"
  
    if isinstance(object, dict):
        key_value_pairs = []
        visited_copy = visited.copy()
        for key, value in object.items():
            stringified_pair = f'{jsonStringify(str(key), visited_copy)}:{jsonStringify(value, visited_copy)}'
            key_value_pairs.append(stringified_pair)
        return f"{{{','.join(key_value_pairs)}}}"

4. Special Numeric Values

JSON doesn't support special numeric values like NaN, Infinity, or -Infinity, but Python's float type does. The current implementation would incorrectly convert these to their string representations.

Problem Example:

jsonStringify(float('nan'))   # Returns: "nan" (invalid JSON)
jsonStringify(float('inf'))   # Returns: "inf" (invalid JSON)

Solution:

import math

if isinstance(object, (int, float)):
    if math.isnan(object) or math.isinf(object):
        return 'null'  # Standard JSON practice
    return str(object)
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement recursion?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More