TypeDrop

2026-08-05 Challenge

2026-08-05 Easy

Typed Safe JSON Parser & Result Unwrapper

You're building a lightweight data-ingestion utility for a dashboard that consumes JSON payloads from third-party webhooks. Payloads can be malformed, partially missing required fields, or entirely the wrong shape — so every parse must return a typed `Result<T, ParseError>` instead of throwing, and callers must exhaustively handle both branches.

Goals

  • Implement the `ok` and `err` smart constructors and use them consistently so callers never construct `Result` objects by hand.
  • Implement `getString` and `getNumber` using `typeof` narrowing on `unknown` — no type assertions allowed.
  • Implement `parseOrderPayload` and `parseRefundPayload` with fail-fast error propagation, returning the first `ParseError` encountered.
  • Make `describeError` exhaustively handle every `ParseError` variant so TypeScript raises a compile error if a new variant is ever added without updating the function.
challenge.ts
// Key types — understand these before writing any logic

export type Ok<T>  = { readonly kind: "ok";  readonly value: T };
export type Err<E> = { readonly kind: "err"; readonly error: E };
export type Result<T, E> = Ok<T> | Err<E>;

export type ParseError =
  | { readonly kind: "invalid_json";  readonly raw: string }
  | { readonly kind: "missing_field"; readonly field: string }
  | { readonly kind: "wrong_type";    readonly field: string; readonly expected: string };

export interface OrderPayload {
  readonly orderId: string;
  readonly customerId: string;
  readonly totalCents: number;
  readonly placedAt: string;
}

// Core functions you must implement:
export function safeParseJSON(raw: string): Result<unknown, ParseError> { ... }
export function getString(obj: unknown, field: string): Result<string, ParseError> { ... }
export function parseOrderPayload(raw: string): Result<OrderPayload, ParseError> { ... }
export function unwrapOr<T, E>(result: Result<T, E>, fallback: T): T { ... }
export function describeError(error: ParseError): string { ... }
Hints (click to reveal)

Hints

  • For `getString`/`getNumber`, first check `typeof obj === 'object' && obj !== null` before indexing — TypeScript will narrow `obj` to `object`, then use `field in obj` to confirm the key exists.
  • For exhaustiveness in `describeError`, add a `default` branch that assigns `error` to a variable typed `never` — the compiler will error if any variant slips through.
  • In `parseOrderPayload`, chain your field extractions imperatively: call `getString(parsed, 'orderId')`, check `.kind === 'err'` and early-return, then move to the next field — this is simpler than monadic `flatMap` for a first pass.

Or clone locally

git clone -b challenge/2026-08-05 https://github.com/niltonheck/typedrop.git