Errors and JSON
Why it matters
Programs break: the user typed letters instead of a number, the server sent corrupted data, a file is
missing. Without handling, one such error stops the whole program. The try … catch construct lets
you catch the error and decide what to do: show a message, use a default value, try again.
And JSON is the main data exchange format on the web: server responses come in it, settings are stored in it. Parsing "someone else's" JSON is the most common place where error handling is needed, which is why these topics are together.
What an error is
console.log("start");
const user = null;
console.log(user.name); // TypeError — the program stops here
console.log("end"); // won't run
Output:
start
TypeError: Cannot read properties of null (reading 'name')
The most common kinds of errors:
| Error | When |
|---|---|
SyntaxError |
the code breaks the rules (or the JSON is corrupted) |
ReferenceError |
accessing a variable that doesn't exist |
TypeError |
the action doesn't fit the type: calling a non-function, .name of null |
RangeError |
a value out of the allowed range |
try … catch
try {
console.log("trying");
const user = null;
console.log(user.name);
console.log("we won't get here");
} catch (error) {
console.log("Caught:", error.name, "-", error.message);
}
console.log("the program keeps working");
Output:
trying
Caught: TypeError - Cannot read properties of null (reading 'name')
the program keeps working
The code in try runs as usual. If an error happens in it, execution jumps straight to catch, and
the error object goes into the variable error. It has name (the kind) and message (the text).
If there was no error, catch is skipped.
If you don't need the error object, you can omit the parentheses: catch { … }.
finally
The finally block runs always — whether there was an error or not. "Cleanup" goes there: close a
file, hide a loading indicator.
try {
console.log("loading...");
} catch (e) {
console.log("error");
} finally {
console.log("hide the spinner");
}
Output:
loading...
hide the spinner
throw — throwing your own error
Your own functions can also report a problem by "throwing" an error:
function buy(price, money) {
if (price > money) {
throw new Error("Not enough money");
}
return money - price;
}
try {
console.log(buy(50, 100));
console.log(buy(500, 100));
} catch (e) {
console.log("Error:", e.message);
}
Output:
50
Error: Not enough money
throw stops the function like return, but instead of a result an error "flies" — up to the nearest
catch higher in the call chain. You can throw a specific kind too: new TypeError(…), new RangeError(…).
Catching only your errors
Sometimes you need to handle one kind of error and let the others pass:
try {
doSomething();
} catch (e) {
if (e instanceof RangeError) {
console.log("we'll fix it");
} else {
throw e; // not ours — rethrow
}
}
Catching "everything" and silently ignoring it is a bad habit: real bugs will hide.
JSON
JSON (JavaScript Object Notation) is a text format similar to JS objects:
{
"name": "Anna",
"age": 15,
"skills": ["html", "css"],
"admin": false,
"pet": null
}
The rules are stricter than for JS objects: keys and strings only in double quotes, no comma after the
last element, no comments, no functions and no undefined.
Object → JSON string: JSON.stringify
const user = { name: "Anna", age: 15, hobbies: ["drawing"] };
const text = JSON.stringify(user);
console.log(text);
console.log(typeof text);
Output:
{"name":"Anna","age":15,"hobbies":["drawing"]}
string
JSON.stringify(obj, null, 2) — pretty output with 2-space indentation.
JSON string → object: JSON.parse
const data = JSON.parse('{"title": "JS course", "lessons": 16}');
console.log(data.lessons + 1);
Output:
17
If the string isn't valid JSON, JSON.parse throws a SyntaxError:
try {
JSON.parse("{name: 'Anna'}"); // a key without quotes, single quotes
} catch (e) {
console.log(e.name);
}
Output:
SyntaxError
JSON.parse(JSON.stringify(obj)) is an old way to make a deep copy of a simple object.
How it works inside
When an error is thrown, the engine "unwinds" the call stack: it leaves the current function, then the one
that called it, and so on, until it finds a try with a catch. If it doesn't, the program stops and the
error is printed to the console. That's why catch can be placed not right around the dangerous line but
where you know what to do with the error.
An error object also has a stack property — the list of functions the error "flew" through. It helps a
lot in finding where things broke.
Common mistakes
throw "text"instead ofthrow new Error("text"). A string has noname,messageandstack. Always throw an error object.- An empty
catch.catch {}silently swallows any problems — bugs become impossible to find. tryaround asynchronous code. Such atrywon't catch an error inside asetTimeoutcallback.- Single quotes in JSON.
JSON.parse("{'a': 1}")→SyntaxError. JSON uses only double quotes. - Forgetting that
stringifyreturns a string.JSON.stringify(obj).nameisundefined.
Cheat sheet
| What | How |
|---|---|
| Catch an error | try { … } catch (e) { … } |
| Always run | finally { … } |
| Throw | throw new Error("text") |
| A specific kind | new TypeError(…), new RangeError(…) |
| Check the kind | e instanceof RangeError |
| Text and kind | e.message, e.name |
| Rethrow | throw e; |
| To a JSON string | JSON.stringify(obj) |
| From a JSON string | JSON.parse(text) |