Skip to content

JSON and Objects

Web Storage only stores strings. If you pass anything else to setItem, the browser converts it to a string silently:

localStorage.setItem('demo:num', 42);
console.log(localStorage.getItem('demo:num')); // "42" — a string, not a number
typeof localStorage.getItem('demo:num'); // "string"
localStorage.setItem('demo:bool', true);
console.log(localStorage.getItem('demo:bool')); // "true" — a string, not a boolean
localStorage.setItem('demo:obj', { key: 'value' });
console.log(localStorage.getItem('demo:obj')); // "[object Object]" — useless!

This means you cannot store objects, arrays, numbers, or booleans directly and expect to read them back in their original type.

The standard pattern is to serialise with JSON.stringify on write and deserialise with JSON.parse on read:

const user = { name: 'Ada', age: 36, admin: false };
// Write
localStorage.setItem('demo:user', JSON.stringify(user));
// Read back
const raw = localStorage.getItem('demo:user');
const restored = raw !== null ? JSON.parse(raw) : null;
console.log(restored.name); // 'Ada'
console.log(restored.age); // 36 (number, not string)
console.log(restored.admin); // false (boolean, not string)

JSON.stringify handles nested objects, arrays, numbers, booleans, and null. It does not preserve:

  • undefined values (they are omitted)
  • Functions (they are omitted)
  • Date objects (converted to ISO string — new Date(str) to restore)
  • Map, Set, RegExp, BigInt (not JSON-serialisable without custom replacer)

Rather than repeating the stringify/parse pattern everywhere, a small helper keeps it clean:

function storageGet(key, fallback) {
try {
const raw = localStorage.getItem(key);
return raw !== null ? JSON.parse(raw) : fallback;
} catch {
return fallback; // Guard against corrupt/non-JSON values
}
}
function storageSet(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}

The try/catch around JSON.parse is important: if the stored value was written by old code (plain string), a third-party script, or was manually edited in DevTools, it may not be valid JSON and JSON.parse will throw.

Browser Storage
OptionBenefitCost
JSON.stringify/JSON.parseHandles deeply nested objects and arrays fully, same pattern everywhereMust re-parse on every read, costing more CPU as data grows
Storing a raw string (no stringify)Faster, no serialization overheadAlways comes back the wrong type (numbers/booleans become strings), and objects become "[object Object]"
Typed storageGet/storageSet helpersShorter call sites, built-in try/catch guards against corrupt dataAdds a small extra layer the team needs to know about and maintain
  • Passing an object straight into setItem without stringifying it — you get "[object Object]" back. Always JSON.stringify before writing.
  • Calling JSON.parse without a try/catch — if the stored value isn’t valid JSON (hand-edited in DevTools, or left over from an old code version), the app crashes immediately.
  • Expecting a Date to come back as a Date object after parsing — JSON.stringify always converts a Date to an ISO string. You must call new Date(str) yourself to restore the original type.
You store a JavaScript object with localStorage.setItem("x", myObj) without stringifying. What is read back?
Which method converts a JavaScript value to a JSON string for storage?
After storing a number with localStorage.setItem("n", 42) (no JSON.stringify), what is typeof localStorage.getItem("n")?
Why should you wrap JSON.parse in a try/catch when reading from localStorage?