JSON is a promise nobody signed
Data from a server arrives as text. JSON.parse returns any — TypeScript can't know what's inside. Describe the shape you expect, then *check* it at runtime with a type guard, and return a Result instead of throwing, so callers must handle failure.
Types for the café API
type Category = "food" | "drink";
interface MenuItem {
id: string;
name: string;
price: number;
category: Category;
vegetarian?: boolean;
}
type Result<T> = { ok: true; value: T } | { ok: false; error: string };
function isMenuItem(x: unknown): x is MenuItem {
if (typeof x !== "object" || x === null) return false;
const o = x as Record<string, unknown>;
return (
typeof o.id === "string" &&
typeof o.name === "string" &&
typeof o.price === "number" &&
(o.category === "food" || o.category === "drink") &&
(o.vegetarian === undefined || typeof o.vegetarian === "boolean")
);
}
function parseMenu(json: string): Result<MenuItem[]> {
let data: unknown;
try {
data = JSON.parse(json);
} catch {
return { ok: false, error: "Invalid JSON" };
}
if (!Array.isArray(data)) return { ok: false, error: "Expected a list" };
if (!data.every(isMenuItem)) return { ok: false, error: "Bad menu item" };
return { ok: true, value: data };
}