JSON.parse checks JSON syntax, not your application’s shape. Its standard TypeScript return type is any for historical compatibility. Assign the result immediately to unknown so unchecked assumptions do not spread: const value: unknown = JSON.parse(text).
Two independent failures are possible. Malformed text such as ”{” cannot be parsed. Valid JSON such as {"minutes":"many"} can be parsed but still be unsuitable for your application. Handle both before returning a trusted Session.
Use try/catch for the syntax operation. Under strict checking, a caught error is unknown. If you want its message, first test error instanceof Error; thrown values are not guaranteed to be Error objects. A short user-facing parser can also return its own stable message rather than exposing implementation details.
Return a discriminated Result instead of letting expected invalid input crash the program. The success variant holds trusted data; the error variant holds a useful message. Keep the try block narrow so a later programming bug is not silently relabeled as invalid JSON.
A successful parse followed by validation establishes a boundary. Functions after that boundary can accept Session and focus on their job. Repeating the full validator inside every arithmetic helper would obscure that separation.
Predict, check, then run
Before running, write down the output and explain which information the checker knows. Click Check types first, then Check & run.
function readText(json: string): string {
let value: unknown;
try {
value = JSON.parse(json);
} catch {
return "Invalid JSON";
}
if (typeof value !== "string") {
return "Expected text";
}
return value.trim();
}
console.log(readText('" Types "'));
console.log(readText("25"));
console.log(readText("{"));Enable JavaScript to edit and type-check this example.
Check your prediction
Types
Expected text
Invalid JSON Read a real compiler error
The program can catch a value of any runtime type. The checker does not assume it is an Error instance.
try {
JSON.parse("{");
} catch (error) {
console.log(error.message);
}Enable JavaScript to edit and type-check this example.
Check your diagnosis
Use error instanceof Error before reading message, or show your own stable parser message. An assertion would not prove what was thrown.
Separate syntax failure from domain failure
JSON.parse can succeed with data of the wrong type. Catching a syntax error is therefore not sufficient validation. Store the parsed result as unknown, then narrow its shape and fields before returning a trusted value.
A Result union can distinguish expected failure from a valid zero or empty collection. Keep the discriminant explicit. Returning null or a fallback for every problem may hide whether the text was malformed or simply described the wrong domain object.
function read(text: string): string {
let value: unknown;
try {
value = JSON.parse(text);
} catch {
return "Syntax error";
}
return typeof value === "number" ? `Number ${value}` : "Wrong shape";
}
console.log(read("0"));
console.log(read('"0"'));
console.log(read("{"));Enable JavaScript to edit and type-check this example.
Check your prediction
Number 0
Wrong shape
Syntax error The string zero is valid JSON but not the numeric shape this function expects. Each output identifies a distinct boundary instead of treating every failure as a parse exception.
type Result =
| {
ok: true;
value: number;
}
| {
ok: false;
message: string;
};
function label(result: Result): string {
return result.ok ? String(result.value) : result.message;
}
console.log(label({ ok: true, value: 0 }));
console.log(label({ ok: false, message: "Invalid count" }));Enable JavaScript to edit and type-check this example.
Check your prediction
0
Invalid count The union makes zero a success independently of its truthiness. A later decoder can add integer and range rules before constructing that success branch.
Try the rule on new inputs
Classify malformed text, valid null, valid wrong-type text and valid data. State which stage rejects each case and preserve the original application state until both parsing and decoding succeed.
Your turn
JSON & Result Types: practise
Write parseCount(json) returning { kind:“ok”; count:number } or { kind:“error”; message:string }. Invalid syntax uses “Invalid JSON”. Valid JSON that is not a finite nonnegative integer uses “Invalid count”. Display 0, Invalid count and Invalid JSON for “0”, ‘“5”’ and ”{”.
// Parse to unknown, validate the count, then return a truthful result variant.Enable JavaScript to edit and type-check this example.
Hint 1: the idea
Syntax validation and domain validation are different stages with different messages.
Hint 2: a first step
Use a narrow try/catch around JSON.parse, then numeric guards outside that block.
Show one solution
type Result =
| {
kind: "ok";
count: number;
}
| {
kind: "error";
message: string;
};
function parseCount(json: string): Result {
let value: unknown;
try {
value = JSON.parse(json);
} catch {
return { kind: "error", message: "Invalid JSON" };
}
if (
typeof value !== "number" ||
!Number.isFinite(value) ||
!Number.isInteger(value) ||
value < 0
) {
return { kind: "error", message: "Invalid count" };
}
return { kind: "ok", count: value };
}
function display(result: Result): string {
return result.kind === "ok" ? String(result.count) : result.message;
}
console.log(display(parseCount("0")));
console.log(display(parseCount('"5"')));
console.log(display(parseCount("{")));Enable JavaScript to edit and type-check this example.
0
Invalid count
Invalid JSONExplain the types at the function boundary and the runtime checks, if any. Compare your approach before copying this answer.
Change it and explain it
Test JSON null, an object, -1, 2.5 and true. Explain why the quoted numeric string is rejected even though Number(“5”) would work. This boundary validates a numeric JSON contract instead of silently coercing it.
Completion is saved in this browser only. It does not measure mastery.