Table of Contents#
- What is
JSON.stringify()? - What is a Function Expression?
- Why
JSON.stringify()Fails with Function Expressions - What Happens When You Try to Stringify a Function?
- Workarounds and Alternatives
- Practical Implications
- Conclusion
- References
What is JSON.stringify()?#
JSON.stringify() is a built-in JavaScript method that converts a JavaScript value (typically an object or array) into a JSON string. Its primary purpose is to serialize data for storage (e.g., localStorage) or transmission (e.g., over HTTP APIs).
Syntax:
JSON.stringify(value[, replacer[, space]]);value: The JavaScript value to serialize.replacer(optional): A function or array to transform values before serialization.space(optional): Adds indentation for readability.
Example of Success:
const user = { name: "Alice", age: 30, isStudent: false };
const jsonString = JSON.stringify(user);
console.log(jsonString);
// Output: '{"name":"Alice","age":30,"isStudent":false}'Here, user contains valid JSON data types, so JSON.stringify() works seamlessly.
What is a Function Expression?#
A function expression is a way to define a function as part of an expression (e.g., assigning it to a variable or object property). Unlike function declarations (e.g., function greet() {}), function expressions are not hoisted and are often used to create anonymous functions or methods.
Examples:
// Anonymous function expression
const add = function(a, b) { return a + b; };
// Arrow function expression (a concise form)
const multiply = (a, b) => a * b;
// Function expression as an object method
const calculator = {
sum: function(a, b) { return a + b; },
product: (a, b) => a * b
};In JavaScript, functions are first-class citizens, meaning they can be stored as object properties, passed as arguments, or returned from other functions. This flexibility often leads developers to include functions in objects—only to encounter issues when serializing with JSON.stringify().
Why JSON.stringify() Fails with Function Expressions#
To understand why JSON.stringify() can’t handle function expressions, we need to examine JSON’s design constraints and purpose.
3.1 JSON’s Limited Data Type Support#
JSON (JavaScript Object Notation) is a lightweight data interchange format with strict rules for allowed data types. According to the JSON specification (ECMA-404), valid JSON values are limited to:
- Objects (
{ ... }) - Arrays (
[ ... ]) - Primitive values: strings, numbers, booleans (
true/false), andnull.
Functions are not included in this list.
When JSON.stringify() encounters a value that isn’t a valid JSON type (like a function), it behaves as follows:
- For object properties: It skips the property entirely.
- For array elements: It replaces the function with
null.
3.2 Security Risks of Serializing Code#
Allowing functions in JSON would introduce severe security vulnerabilities. JSON is designed to be parsed safely via JSON.parse(), but functions are executable code. If JSON.stringify() included functions, an attacker could craft a malicious JSON string containing harmful code (e.g., eval('rm -rf /')). When parsed, this code could execute, leading to data breaches or system compromise.
To prevent this, JSON.parse() explicitly ignores code: it will not execute functions, even if they are included as strings. However, JSON.stringify() proactively avoids serializing functions to prevent accidental or malicious code injection.
3.3 JSON’s Design Philosophy: Data, Not Code#
JSON was created by Douglas Crockford in 2001 as a "data interchange format"—a language-agnostic way to represent structured data. Its goal is to transmit data, not logic (code). Functions embody logic, not data, so they have no place in JSON.
As Crockford put it: "JSON is not a document format. It is not a markup language. It is a data interchange format." Functions are code, not data, so they violate this core principle.
What Happens When You Try to Stringify a Function?#
Let’s test JSON.stringify() with an object containing a function expression to see its behavior.
Example 1: Function as an Object Property#
const person = {
name: "Bob",
age: 25,
greet: function() { return `Hello, I'm ${this.name}`; } // Function expression
};
const jsonPerson = JSON.stringify(person);
console.log(jsonPerson);
// Output: '{"name":"Bob","age":25}' (the `greet` function is omitted!)Why? The greet function is not a valid JSON type, so JSON.stringify() skips it by default.
Example 2: Forcing Functions with a Replacer#
You might try to use the replacer parameter to "force" JSON.stringify() to include functions. For example, converting the function to a string:
const jsonWithFunction = JSON.stringify(person, (key, value) => {
// Convert functions to their string representation
return typeof value === "function" ? value.toString() : value;
});
console.log(jsonWithFunction);
// Output: '{"name":"Bob","age":25,"greet":"function() { return `Hello, I'm ${this.name}`; }"}'Now the greet function is included as a string. But this is not true serialization:
// Parsing the string back into an object
const parsedPerson = JSON.parse(jsonWithFunction);
console.log(parsedPerson.greet);
// Output: "function() { return `Hello, I'm ${this.name}`; }" (a string, not a function!)parsedPerson.greet is now a string, not an executable function. To recover the function, you’d need to use eval() (e.g., parsedPerson.greet = eval(parsedPerson.greet)), but this is extremely unsafe (as eval() executes any code, opening the door to attacks).
Example 3: Functions in Arrays#
If a function is an array element, JSON.stringify() replaces it with null:
const arr = [1, "two", function() { return 3; }];
console.log(JSON.stringify(arr));
// Output: '[1,"two",null]' (function replaced with null)Workarounds and Alternatives#
If you must serialize an object with functions (e.g., for state management in frameworks), consider these approaches (with caveats):
1. Manual String Conversion (Not Recommended)#
As shown earlier, you can use value.toString() in a replacer to store the function as a string. However, this requires manual reconstruction with eval() or new Function(), which is unsafe for untrusted data.
// Serialize with replacer
const serialized = JSON.stringify(person, (k, v) =>
typeof v === "function" ? v.toString() : v
);
// Reconstruct (UNSAFE!)
const deserialized = JSON.parse(serialized);
deserialized.greet = new Function(deserialized.greet); // or eval(deserialized.greet)2. Use a Serialization Library#
Libraries like flatted or serialize-javascript can serialize functions by converting them to strings and reconstructing them safely. However, these tools are not JSON-compatible and may introduce dependencies.
Example with serialize-javascript:
import serialize from 'serialize-javascript';
const serialized = serialize(person);
// Includes the function as a string: '{"name":"Bob","age":25,"greet":function() { return `Hello, I'm ${this.name}`; }}'
// To parse, use eval (still risky, but the library escapes dangerous code)
const deserialized = eval(`(${serialized})`);3. Separate Data and Logic#
The cleanest solution is to avoid including functions in data objects. Store only data in JSON, and define functions separately (e.g., on the prototype or in a class).
// Data (serializable)
const userData = { name: "Charlie", age: 35 };
// Logic (defined separately)
class User {
constructor(data) {
this.name = data.name;
this.age = data.age;
}
greet() {
return `Hello, I'm ${this.name}`;
}
}
// Usage: Deserialize data, then wrap in User class to add logic
const jsonUserData = JSON.stringify(userData);
const parsedData = JSON.parse(jsonUserData);
const user = new User(parsedData);
user.greet(); // "Hello, I'm Charlie" (works!)Practical Implications#
Understanding this limitation is critical for real-world development:
- API Communication#
APIs exchange data, not code. If you send an object with functions to a backend, the functions will be stripped, leading to missing data. Always send raw data and define functions on the server separately.
- State Management#
In frameworks like React or Redux, state should be serializable. Including functions in state (e.g., useState({ data: ..., func: () => {} })) will cause the function to be lost when persisting state to localStorage.
- localStorage/sessionStorage#
These storage APIs only accept strings, so JSON.stringify() is required. Functions in stored objects will be silently omitted, leading to broken behavior when retrieving the state.
Conclusion#
JSON.stringify() cannot serialize function expressions because JSON is designed for data interchange, not code. Its strict data type rules, security safeguards, and focus on simplicity exclude functions by design. While workarounds exist, they often introduce risks or complexity.
The best practice is to separate data (serializable) from logic (functions). By keeping JSON focused on data, we ensure safety, compatibility, and adherence to its core purpose as a universal data format.