Checkpoint: Validated Payloads

The Course
•
4 min read

Combine a branded id factory and an unknown-data decoder. The returned success must contain a new flat object with only id and title; input extras must not leak through a spread. Both strings are trimmed and blanks are rejected.

The brand’s rule is only “nonblank normalized string,” not uniqueness or database existence. A readonly payload is a TypeScript permission contract, not a runtime freeze. These distinctions will matter when event subscribers receive the decoded record.

Use about twenty minutes. Try invalid records before revealing the answer: null, missing id, blank title and a numeric title. A checker accepting the decoder signature is only one layer of evidence.

Predict, check, and run

Write the output before running. Use Check types first, then Check & run. Types disappear at runtime: explain both what the compiler checks and what the code actually does. Each editor is an independent ES module; await asynchronous work explicitly.

Predict, then runTypeScript
const original = { title: " Types ", secret: true };
const projected = { title: original.title.trim() };

console.log(Object.keys(projected).join(","));
console.log(original.title === " Types ");

Enable JavaScript to edit and type-check this example.

Check your prediction
title
true

Acceptance cases before you code

Write a small test plan before implementing validated branded payload. 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 tryWhat your result should demonstrate
Padded nonblank id and titleReturn the normalized new record
Null, blank title or nontext fieldReject before construction
Extra source propertyExclude it while leaving the source unchanged

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.

Build it yourself

Exercise

Checkpoint: Validated Payloads: practise

Write a Result-returning decoder for a readonly {id:BookId;title:string}. Use a checked id factory, validate nonblank title, and project the two fields. Print b1 Types and id,title for a valid input with extra fields. Assert null and blank title fail.

Your workspaceTypeScript
// Start from the stated contract. Add a valid call and an edge case.

Enable JavaScript to edit and type-check this example.

Hint 1: the idea

Validate the record before its fields, and construct success only after both fields pass.

Hint 2: a first step

Reuse the branded id factory; keep its assertion out of the payload decoder.

Show one solution
One possible solutionTypeScript
type Result<T> =
  | {
      ok: true;

      value: T;
    }
  | {
      ok: false;

      message: string;
    };

declare const brand: unique symbol;

type BookId = string & { readonly [brand]: "BookId" };

function idOf(value: unknown): BookId | null {
  if (typeof value !== "string" || value.trim() === "") {
    return null;
  }

  return value.trim() as BookId;
}

type Payload = {
  readonly id: BookId;

  readonly title: string;
};

function decode(value: unknown): Result<Payload> {
  if (
    typeof value !== "object" ||
    value === null ||
    !("id" in value) ||
    !("title" in value)
  ) {
    return { ok: false, message: "Expected record" };
  }

  const id = idOf(value.id);

  if (
    id === null ||
    typeof value.title !== "string" ||
    value.title.trim() === ""
  ) {
    return { ok: false, message: "Invalid fields" };
  }

  return { ok: true, value: { id, title: value.title.trim() } };
}

for (const bad of [null, { id: "b1", title: " " }]) {
  if (decode(bad).ok) {
    throw new Error("Invalid payload accepted");
  }
}

const result = decode({ id: " b1 ", title: " Types ", extra: 1 });

if (!result.ok) {
  throw new Error(result.message);
}

console.log(result.value.id, result.value.title);
console.log(Object.keys(result.value).join(","));

Enable JavaScript to edit and type-check this example.

b1 Types
id,title

Trace the implementation against the task before comparing styles. A passing example supports that case; use the changes below to challenge the wider contract.

Change it and test it

Assert the original object is unchanged. Try direct assignment to the returned title in the editor. Explain why that compiler error is separate from dropping extra fields at runtime.

Was this article helpful?

Your response is saved on this device.