JSON and Objects
Everything is a string
Section titled “Everything is a string”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 numbertypeof 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.
Storing objects with JSON.stringify
Section titled “Storing objects with JSON.stringify”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 };
// WritelocalStorage.setItem('demo:user', JSON.stringify(user));
// Read backconst 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:
undefinedvalues (they are omitted)- Functions (they are omitted)
Dateobjects (converted to ISO string —new Date(str)to restore)Map,Set,RegExp,BigInt(not JSON-serialisable without custom replacer)
A tiny typed helper
Section titled “A tiny typed helper”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.
Runnable: objects round-trip
Section titled “Runnable: objects round-trip”Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
JSON.stringify/JSON.parse | Handles deeply nested objects and arrays fully, same pattern everywhere | Must re-parse on every read, costing more CPU as data grows |
| Storing a raw string (no stringify) | Faster, no serialization overhead | Always comes back the wrong type (numbers/booleans become strings), and objects become "[object Object]" |
Typed storageGet/storageSet helpers | Shorter call sites, built-in try/catch guards against corrupt data | Adds a small extra layer the team needs to know about and maintain |
Common mistakes
Section titled “Common mistakes”- Passing an object straight into
setItemwithout stringifying it — you get"[object Object]"back. AlwaysJSON.stringifybefore writing. - Calling
JSON.parsewithout atry/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
Dateto come back as aDateobject after parsing —JSON.stringifyalways converts aDateto an ISO string. You must callnew Date(str)yourself to restore the original type.