Section 4 · JavaScript in the Browser
Saving Data: JSON and localStorage
16 min3 min readCode playground
Lesson at a glance
Turn data into JSON text and back with JSON.stringify and JSON.parse, know what JSON cannot hold, and save and load Pocket’s expenses with localStorage safely.
No video here — just the real thing: a written lesson, runnable examples, a quiz and practice.
Add three expenses to Pocket, reload the page, and they are gone. Everything you built lives in JavaScript variables, and variables only last as long as the page. To remember data between visits you need to store it somewhere, and the simplest place a web page can store data is in the user’s own browser. First, though, the data has to become text.
JSON: data as text
JSON (JavaScript Object Notation) is a text format for data. It looks almost exactly like JavaScript object and array literals, which is no coincidence, and today it is the most common format for sending data between programs on the web.
const expenses = [
{ id: "exp-1", label: "Coffee", amount: 450, tags: ["work"] },
{ id: "exp-2", label: "Train", amount: 1220, tags: [] },
];
const text = JSON.stringify(expenses);
console.log(typeof text);
console.log(text);
const back = JSON.parse(text);
console.log(back[1].label, back === expenses);JSON.stringify turns a value into a JSON string. JSON.parse reads a JSON string and builds new values from it. The result is a fresh copy with the same contents, which is why back === expenses is false.
JSON’s rules are stricter than JavaScript’s: property names must be in double quotes, strings use double quotes only, and trailing commas are not allowed. You rarely write JSON by hand, but when you do, those three rules cause most parse errors. For a readable version, JSON.stringify(value, null, 2) indents the output by two spaces.
What JSON cannot hold
JSON knows strings, numbers, booleans, null, arrays and plain objects. Anything else is changed or lost on the way through:
const tricky = {
when: new Date("2026-10-05T09:30:00Z"),
note: undefined,
format() { return "hi"; },
tags: new Set(["work"]),
ratio: NaN,
};
console.log(JSON.stringify(tricky));Dates become ISO strings and do not come back as dates, undefined properties and functions are dropped, Sets and Maps become empty objects, and NaN becomes null. Pocket avoids all of this by design: amounts are whole numbers, dates are stored as "2026-10-05" strings, and categories are plain strings. Choosing JSON-friendly data from the start saves a lot of conversion code later.
localStorage: a small store in the browser
Every website gets a small key-value store in each user’s browser, called localStorage. It survives reloads and browser restarts, and it holds strings only:
localStorage.setItem("pocket.theme", "dark");
console.log(localStorage.getItem("pocket.theme")); // "dark"
console.log(localStorage.getItem("nothing-here")); // null
localStorage.removeItem("pocket.theme");To store an array of expenses, turn it into JSON on the way in and parse it on the way out:
const STORAGE_KEY = "pocket.expenses.v1";
function saveExpenses(expenses) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(expenses));
}
function loadExpenses() {
try {
const text = localStorage.getItem(STORAGE_KEY);
const data = text === null ? [] : JSON.parse(text);
return Array.isArray(data) ? data : [];
} catch {
return [];
}
}loadExpenses is defensive on purpose, because it runs on every page load and must never crash the app. A first-time visitor has nothing saved, so getItem returns null and we start with an empty list. Corrupted text makes JSON.parse throw, and the catch falls back to an empty list. Some browsers’ privacy modes block storage entirely, and that throws too, into the same catch. Finally, Array.isArray guards against valid JSON of the wrong shape. The .v1 in the key lets a future version of Pocket change the data format and migrate old data instead of misreading it.
Then wire it up: load once at start-up, and save every time the data changes, right where you already call renderExpenses:
let expenses = loadExpenses();
renderExpenses();
function setExpenses(next) {
expenses = next;
saveExpenses(expenses);
renderExpenses();
}Routing every change through one setExpenses function means you cannot forget to save in one handler and remember in another.
To see what is stored, open DevTools, go to the Application panel in Chrome or Edge (Storage in Firefox), and expand Local Storage. You can read, edit and delete entries there, which is the quickest way to test what your app does with missing or broken data. There is also sessionStorage, with the same methods, which is cleared when the tab is closed.
You can now keep data across visits. The other big source of data is a server on the internet, and talking to one means waiting for an answer. Waiting is the subject of the next lesson.
Key takeaways
- JSON is a text format for data;
JSON.stringifyturns values into JSON text andJSON.parseturns JSON text back into values. - JSON holds strings, numbers, booleans, null, arrays and plain objects; functions and
undefinedare dropped, and dates become strings. localStoragestores strings only, per website, in the user’s browser; save withsetItem(key, JSON.stringify(data)).- Loading must survive a missing key (
getItemreturnsnull) and corrupted text (JSON.parsethrows), so wrap it intry/catchwith a fallback.