Lodash isEqual vs JSON.stringify in JavaScript
Using JSON.stringify to compare objects is a common
shortcut in JavaScript, but it introduces subtle bugs that can break
application logic. While string serialization appears to offer an easy
way to check deep equality, it fails to handle structural nuances,
non-JSON data types, and cyclic references. In contrast, Lodash’s
_.isEqual performs true semantic deep comparisons,
correctly inspecting object shapes and diverse data types. This article
breaks down the architectural flaws of using JSON.stringify
for equality checks and explains why _.isEqual is the
superior, production-ready solution.
Key Order Sensitivity
JavaScript objects represent unordered collections of key-value
pairs. However, JSON.stringify serializes keys based on
their property enumeration order. If two objects contain identical keys
and values created in different sequences, JSON.stringify
produces different strings:
const objA = { name: "Alice", role: "Admin" };
const objB = { role: "Admin", name: "Alice" };
JSON.stringify(objA) === JSON.stringify(objB); // false
_.isEqual(objA, objB); // true_.isEqual traverses keys structurally, ignoring
insertion order and comparing only whether the properties and their
corresponding values match.
Loss of Non-JSON Data Types
JSON format strictly supports strings, numbers, booleans, arrays,
objects, and null. When JSON.stringify
encounters other common JavaScript types, it either converts them or
omits them entirely:
undefined, Functions, and Symbols: Omitted when found in objects, or converted tonullinside arrays.NaNandInfinity: Both serialize directly tonull.DateObjects: Serialized to ISO strings, losing theirDateprototype and type information.MapandSet: Serialized as empty object literals ({}).RegExp: Serialized as an empty object ({}).
const user1 = { id: 1, action: () => {}, created: new Date("2024-01-01") };
const user2 = { id: 1, action: undefined, created: "2024-01-01T00:00:00.000Z" };
JSON.stringify(user1) === JSON.stringify(user2); // true (False Positive)
_.isEqual(user1, user2); // false (Accurate)_.isEqual handles full JavaScript type checking. It
compares Date objects by their millisecond timestamps,
verifies RegExp pattern and flag parity, checks
Map and Set members, and recognizes distinct
prototypes.
Handling Circular References
If an object directly or indirectly references itself, passing it to
JSON.stringify throws an unhandled runtime exception:
const nodeA = {};
nodeA.self = nodeA;
JSON.stringify(nodeA); // Uncaught TypeError: Converting circular structure to JSON_.isEqual maintains an internal cache of visited objects
during its recursive traversal. It safely resolves cyclical graphs
without blowing the call stack or throwing errors.
Performance and Early Exit Optimization
JSON.stringify must serialize the entire object graph
into a continuous string before the equality comparison even begins. If
two large objects differ only by their first property, stringification
still processes the entire data structure from start to finish and
allocates significant memory for both serialized strings.
_.isEqual short-circuits. It performs immediate checks,
failing fast if:
- Object references match identically via reference equality
(
===). - Array lengths or object key counts differ.
- Any single nested property does not match.
This localized, fail-fast evaluation avoids unnecessary traversals and memory allocations on non-matching objects.