Implement decodeBook from unknown to a normalized Book or an Issue. Require nonblank id and title, and finite nonnegative integer pages. Accept zero pages. Return only modeled properties, and leave the source unchanged.
Use a fixed field-check order so failures are predictable: object, id, title, pages. A value failing several rules reports the first failed rule in that order. That is a public diagnostic policy, not proof the remaining fields were valid.
Test each missing field independently, wrong primitive types, blanks, decimals and negative counts. A decoder that passes only one happy-path example has not established its runtime claims. This checkpoint prepares the element decoder used by the capstone’s generic array boundary.
Predict, run, and explain
Write the output and trace the important state changes before checking it. Then run the program. Use Check types first; execution is allowed only when the real compiler accepts the source.
const record: unknown = { id: " b1 ", title: " Types ", pages: 0 };
if (
typeof record === "object" &&
record !== null &&
"id" in record &&
typeof record.id === "string"
) {
console.log(record.id.trim());
}Enable JavaScript to edit and type-check this example.
Check your prediction
b1 Acceptance cases before you code
Write a small test plan before implementing book decoding and output shape. These cases extend the brief; they are not a second worked solution. Predict the expected behavior independently, then test through the public function. A good ordinary example and a good boundary example should catch different possible mistakes.
| Case to try | What your result should demonstrate |
|---|---|
| Zero pages with padded strings | Accept and normalize |
| Text pages or a missing required field | Reject at the relevant field path |
| Extra private input property | Do not copy it into success |
After the core task passes, deliberately break one rule and identify the case that notices it. Restore the implementation and explain the failure in terms of the contract, not the number of assertions. If a case requires a concept you have not recalled, revisit the relevant lesson before opening the solution.
Your turn
Checkpoint: Book Decoder: practise
Define Book and Result<T>, implement decodeBook, and display Types: 0 for valid trimmed input. For a text pages field, print pages. The returned object must exclude any extra private field.
// Validate each field, then construct the clean record explicitly.Enable JavaScript to edit and type-check this example.
Hint 1: the idea
An accepted unknown must have evidence for every promised property.
Hint 2: a first step
Reject a non-null-object mismatch before testing fields with in.
Show one solution
type Book = {
readonly id: string;
readonly title: string;
readonly pages: number;
};
type Result<T> =
| {
ok: true;
value: T;
}
| {
ok: false;
issue: {
path: string;
message: string;
};
};
function decodeBook(value: unknown): Result<Book> {
if (typeof value !== "object" || value === null) {
return { ok: false, issue: { path: "book", message: "Expected object" } };
}
if (
!("id" in value) ||
typeof value.id !== "string" ||
value.id.trim() === ""
) {
return { ok: false, issue: { path: "id", message: "Invalid id" } };
}
if (
!("title" in value) ||
typeof value.title !== "string" ||
value.title.trim() === ""
) {
return { ok: false, issue: { path: "title", message: "Invalid title" } };
}
if (
!("pages" in value) ||
typeof value.pages !== "number" ||
!Number.isFinite(value.pages) ||
!Number.isInteger(value.pages) ||
value.pages < 0
) {
return { ok: false, issue: { path: "pages", message: "Invalid pages" } };
}
return {
ok: true,
value: {
id: value.id.trim(),
title: value.title.trim(),
pages: value.pages
}
};
}
const a = decodeBook({ id: " b1 ", title: " Types ", pages: 0, private: true });
const b = decodeBook({ id: "b2", title: "Types", pages: "10" });
if (a.ok) {
if ("private" in a.value) {
throw new Error("Projection leaked");
}
console.log(`${a.value.title}: ${a.value.pages}`);
}
console.log(b.ok ? "unexpected" : b.issue.path);Enable JavaScript to edit and type-check this example.
Types: 0
pagesExplain why the implementation meets the stated contract. Which part is enforced by the language or checker, and which part needs behavioral evidence?
Change it and test it
Write runtime assertions for every field boundary, including NaN and Infinity supplied directly. Add arrayOf and test a malformed second row. Preserve the first issue path and do not silently drop rejected rows.
Completion is saved in this browser only. It does not measure mastery.