External values begin as unknown when their shape has not been established. Unlike any, unknown forces you to narrow before using properties or treating a value as a particular type. A runtime comparison can provide the evidence the compiler’s control-flow analysis needs.
A discriminated union pairs a literal tag with the payload valid for that branch. StateDecision is either a valid state or an invalid reason. Consumers switch on kind, then use only the payload available in that case.
The exact comparisons value==="ok" or value==="failed" narrow unknown to the two allowed literal values. The returned valid branch then preserves that state. Other inputs remain a normal invalid decision rather than an exception.
In describeState, kind selects the corresponding payload. A never default makes a later added union branch visible to the compiler if the consumer has not been updated. That protects a source relationship; it does not inspect arbitrary runtime objects that bypass the type system.
export type JobState = "ok" | "failed";
export interface Job { service: string; state: JobState; duration: number; }
export interface Group { service: string; count: number; failed: number; totalDuration: number; }
export interface CLIResult { code: number; stdout: string; stderr: string; }
export type StateDecision = { kind: "valid"; state: JobState } | { kind: "invalid"; reason: string };
export function decideState(value: unknown): StateDecision {
if (value === "ok" || value === "failed") return { kind: "valid", state: value };
return { kind: "invalid", reason: "state must be ok or failed" };
}
export function describeState(decision: StateDecision): string {
switch (decision.kind) {
case "valid": return "state: " + decision.state;
case "invalid": return decision.reason;
default: { const unreachable: never = decision; return unreachable; }
}
}
console.log(describeState(decideState("failed")));
console.log(describeState(decideState("pending")));A discriminant should be a stable literal, not a boolean-like string tested by truthiness. The branch payload must agree with it. Returning kind:valid for arbitrary input would break both consumers and the accepted state contract.
The compiler can flag property access on unknown. It cannot ensure a handwritten type predicate tells the truth, so this course demonstrates direct runtime checks and constructs a new result rather than asserting a shape.
const value: unknown = "ok";
if (typeof value === "string") console.log(value.toUpperCase());
else console.log("not text");Implement the unknown-to-decision boundary and inspect the exhaustive consumer. Next validate a complete external JSON record instead of only its state.