Skip to content

Serialization & Transport

Send typed errors across HTTP and reconstruct them with full types.

A NiceError turns into plain JSON, travels over the network, and is rebuilt on the other side — with its ids, context, and HTTP status all intact.

const json = error.toJsonObject(); // plain JSON object — safe over HTTP
const str = error.toJsonString(); // JSON string
const response = error.toHttpResponse(); // a Response with the correct HTTP status

On the receiving side, castNiceError turns any caught value into a NiceError:

import { castNiceError } from "@nice-code/error";
const caught = castNiceError(unknownValue); // always returns a NiceError
if (err_auth.isExact(caught)) {
const hydrated = err_auth.hydrate(caught); // deserialize context
const { username } = hydrated.getContext("invalid_credentials");
}

castNiceError copes with anything you hand it — a NiceError, a JSON object, a plain Error, an error-like object, null/undefined, even a primitive — and it never throws.

When you have a specific domain in mind, castAndHydrate casts, checks the domain, and hydrates in a single call:

import { castAndHydrate, matchFirst } from "@nice-code/error";
const error = castAndHydrate(unknownValue, err_auth);
// → a hydrated NiceError when it belongs to err_auth, otherwise the raw cast NiceError
if (err_auth.isExact(error)) {
const message = matchFirst(error, {
invalid_credentials: ({ username }) => `Wrong password for ${username}`,
account_locked: () => "Account locked",
});
}

Only JSON travels over the network. So if an id’s context held a Date, an Error, or anything with a custom pack/unpack pair, hydration is the step that turns the JSON form back into the real typed value. Always hydrate (or castAndHydrate) before reading context that just came in from somewhere else.

// Server
return error.toHttpResponse();
// Client
const caught = castNiceError(await res.json());
if (err_auth.isExact(caught)) {
const hydrated = err_auth.hydrate(caught);
// hydrated.getContext(...) is now fully typed and real
}