CodeChallenge LogoCodeChallenge

What JSON Can’t Store — Missing Types and Workarounds

JSON handles strings, numbers, booleans, null, objects, and arrays. Everything else — dates, undefined, BigInt, Map, Set — gets lost or corrupted during serialization.

July 25, 2026
5 min read
#javascript #json #serialization #data #types
What JSON Can’t Store — Missing Types and Workarounds

I once spent two hours debugging a profile page that showed the wrong age. The user’s birth date was stored as a string in the database, sent as a string in the API response, and parsed back into a Date object on the frontend — except the parse step used new Date(response.birthDate) on a string that was already in ISO format, which worked locally but failed in a different timezone. The root cause was not the date logic. It was my assumption that JSON would preserve the Date type through the serialization round trip.

JSON is everywhere — API responses, config files, localStorage, WebSocket messages. It is simple, human-readable, and supported in every language. But it only handles six types: string, number, boolean, null, object, and array. Everything else gets converted, truncated, or silently dropped. Here are the types that break and how to handle each one.

Date

Dates are the most common casualty. When you pass a Date object to JSON.stringify, it calls the Date’s .toISOString() method and stores the result as a string. On the receiving end, JSON.parse returns a string, not a Date. If your code expects a Date object and calls .getMonth() on a string, it crashes.

const data = { created: new Date() }
JSON.stringify(data) // '{"created":"2026-07-25T10:00:00.000Z"}'
// The Date is now a string. It will never be a Date again automatically.

The fix is to use a reviver function in JSON.parse that detects ISO date strings and converts them back to Date objects. Or use a custom replacer in JSON.stringify that stores dates in a tagged format.

const str = JSON.stringify(data)
const parsed = JSON.parse(str, (key, val) => {
  if (typeof val === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(val)) {
    return new Date(val)
  }
  return val
})

A cleaner convention is to store a type marker alongside the value: {"__type":"date","value":"2026-07-25"}. The reviver checks for the marker and handles the conversion. This makes the serialization format self-documenting and works across languages.

Undefined

JSON has null but not undefined. When you stringify an object with an undefined property, that property is silently dropped. If the property is inside an array, it becomes null instead. This subtle difference causes bugs when the receiving code checks for prop === undefined and never finds it.

JSON.stringify({ a: undefined, b: 1 }) // '{"b":1}'
JSON.stringify([undefined, 1])         // '[null,1]'

The fix depends on your use case. If the distinction between undefined and null matters, use a custom replacer that converts undefined to a sentinel value like {"__undefined":true}. If it does not matter, convert undefined to null before serialization.

BigInt

BigInt values throw a TypeError when passed to JSON.stringify. JavaScript’s JSON serializer simply does not know how to handle them. This is becoming more common as APIs return large integers from databases or external services.

JSON.stringify({ id: 9007199254740993n })
// TypeError: Do not know how to serialize a BigInt

The fix is to add a replacer that converts BigInt to a string with a marker, then reverse the conversion in the reviver. Or convert BigInt to a string during serialization and accept that it will be a string on the other side.

Map and Set

Map and Set objects serialize to empty objects because they have no enumerable properties in the traditional sense. The entries are stored internally, not as direct properties.

const map = new Map([['key', 'value']])
JSON.stringify(map) // '{}'

The fix is to convert Map to an array of entries before serialization: JSON.stringify([...map]). For Set, convert to an array: JSON.stringify([...set]). On the receiving end, reconstruct the Map or Set from the array.

Circular References

Objects that reference themselves crash JSON.stringify entirely. This happens with DOM references, parent-child relationships in trees, or cached data structures.

Code
const obj = { name: 'parent' }
obj.self = obj
JSON.stringify(obj) // TypeError: Converting circular structure to JSON

The fix is either to remove circular references before serialization (by traversing the object and breaking loops) or to use a library like flatted that encodes circular references with path-based pointers. For simple cases, structuredClone handles circular references natively and can be used as an alternative to JSON round trips.

NaN and Infinity

JSON does not support NaN or Infinity. They are converted to null during serialization, which changes the semantics of your data.

Code
JSON.stringify({ score: NaN, max: Infinity })
// '{"score":null,"max":null}'

The fix is a custom replacer that converts NaN to the string "__NaN__" and Infinity to "__Infinity__", then reverses the conversion in the reviver.

The Simple Solution: structuredClone

If your goal is to deep-clone an object rather than transmit it to another system, structuredClone handles all of these types natively: Date, Map, Set, RegExp, ArrayBuffer, and circular references. It is available in all modern browsers and Node.js 17+. Use it instead of JSON.parse(JSON.stringify(obj)) for deep copies.

Code
const clone = structuredClone(original)
// Preserves Date, Map, Set, RegExp, circular refs

That said, structuredClone cannot be sent over HTTP. For API communication, you still need JSON serialization with custom replacers and revivers. The key is knowing which types your data contains and handling each one explicitly.

Practical Recommendation

Here is a quick rule of thumb for JSON serialization. If you control both the sender and receiver, use ISO strings for dates, convert Map and Set to arrays before serialization, and handle BigInt by converting to string with a marker. Use a shared utility function that wraps JSON.stringify and JSON.parse with the appropriate replacers and revivers so every endpoint benefits from the same handling. The small upfront effort of writing a custom serializer saves hours of debugging corrupted data later.

If you are building an API that other developers consume, document your serialization format explicitly. Tell consumers which fields might contain date strings, which fields can be null vs undefined, and whether BigInt values are sent as strings or numbers. Good documentation prevents the kind of timezone bug I spent two hours debugging on that profile page.

Share:
Found this helpful?

Related Articles