JSON.stringify()feels like a simple, predictable function until it isn't. A field you expected quietly disappears, keys come out in an order you did not specify, or the whole call throws an error with no obvious cause. None of this is random. Each behavior follows a specific rule that is easy to miss the first time you run into it.
JSON.stringify() silently omits any object property whose value is undefined, a function, or a Symbol, rather than throwing an error or including a null placeholder:
JSON.stringify({ name: "Freddy", role: undefined })
// '{"name":"Freddy"}' -- role is just goneThis is intentional, not a bug, since JSON has no way to represent undefined or a function at all. If you actually need that field present with an explicit null, set the value to null before calling stringify rather than leaving it undefined.
In practice, modern JavaScript engines preserve insertion order for string keys, with one exception: keys that look like non-negative integers get sorted numerically and placed before all other keys, regardless of where you wrote them in the source:
JSON.stringify({ b: 1, 2: "two", a: 3 })
// '{"2":"two","b":1,"a":3}'This is part of the ECMAScript specification for property enumeration order, not a quirk of one engine, so it will show up consistently across browsers and Node.js versions. If your code relies on a specific key order for anything other than display purposes, it is worth being aware this rule exists.
If an object contains a reference back to itself, directly or through a chain of other objects, JSON.stringify() throws a TypeError: Converting circular structure to JSON rather than looping forever. This comes up most often with objects that reference their own parent, or with certain framework internals (like some DOM nodes) that have circular structures built in by design. The fix is usually to strip out the circular reference before serializing, either manually or with a custom replacer function passed as the second argument to stringify.
If an object has a toJSON() method defined on it, JSON.stringify()calls that method and serializes its return value instead of the object's own properties. This is how Dateobjects end up as ISO date strings in JSON output rather than a dump of their internal fields. It is worth knowing this exists, since it explains a lot of "why does this object serialize differently than I expected" cases that have nothing to do with the rules above.
When something in a stringified payload does not look right, pasting the result into the JSON Formatter on this site will at least confirm whether the output is valid JSON to begin with, which is a useful first step before chasing down which of the rules above is actually responsible.