denoland/deno · error · TypeError
Value is not JSON serializable
Error message
Value is not JSON serializable
What it means
serializeJSValueToJSONString (ext/web/00_infra.js:330) is the helper behind Response.json() (ext/fetch/23_response.js:700) and other JSON-body infrastructure. It calls JSON.stringify and throws TypeError when stringify returns undefined, which happens exactly when the top-level value is undefined, a function, or a symbol. Nested values of those types are silently dropped by stringify and do not trigger this error.
Source
Thrown at ext/web/00_infra.js:330
/**
* @param {unknown} cond
* @param {string=} msg
* @returns {asserts cond}
*/
function assert(cond, msg = "Assertion failed.") {
if (!cond) {
throw new AssertionError(msg);
}
}
/**
* @param {unknown} value
* @returns {string}
*/
function serializeJSValueToJSONString(value) {
const result = JSONStringify(value);
if (result === undefined) {
throw new TypeError("Value is not JSON serializable");
}
return result;
}
const PATHNAME_WIN_RE = new SafeRegExp(/^\/*([A-Za-z]:)(\/|$)/);
const SLASH_WIN_RE = new SafeRegExp(/\//g);
const PERCENT_RE = new SafeRegExp(/%(?![0-9A-Fa-f]{2})/g);
// Keep in sync with `fromFileUrl()` in `std/path/win32.ts`.
/**
* @param {URL} url
* @returns {string}
*/
function pathFromURLWin32(url) {
let p = StringPrototypeReplace(url.pathname, PATHNAME_WIN_RE, "$1/");
p = StringPrototypeReplace(p, SLASH_WIN_RE, "\\");
p = StringPrototypeReplace(p, PERCENT_RE, "%25");
let path = decodeURIComponent(p);View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Default the payload: Response.json(data ?? null) or Response.json(data ?? {})
- If you passed a function, call it: Response.json(toJson())
- Serialize a wrapper object so the top level is never undefined: Response.json({ value })
Example fix
// before return new Response.json(data.user); // undefined when user missing // after return Response.json(data.user ?? null);
Defensive patterns
Strategy: type-guard
Validate before calling
if (typeof body === "undefined" || typeof body === "function" || typeof body === "symbol") {
body = null;
}
return Response.json(body); Type guard
function isTopLevelJsonSerializable(v: unknown): boolean {
return v == null ||
(typeof v !== "function" && typeof v !== "symbol");
} Try / catch
let str: string;
try {
str = JSON.stringify(value);
} catch {
str = "{}"; // circular reference
}
if (str === undefined) str = "null"; // top-level undefined/function/symbol
return new Response(str, {
headers: { "content-type": "application/json" },
}); Prevention
- Default payloads: Response.json(data ?? null)
- Call functions before serializing; double-check parentheses on method references
- Prefer wrapping in an object ({ value }) so a missing field never becomes the top level
When it happens
Trigger: return Response.json(data.user) where data.user is undefined; Response.json(handler.serialize) with the call parentheses missing, passing the function itself; Response.json(Symbol('id')) as a top-level value.
Common situations: Optional fields from fetch or DB rows used directly as the whole body; passing a method reference instead of its result; refactors that turn a always-object payload into an optional one without a default.
Understand the failure class
Background: "JSON serialization failed", "not JSON serializable", "Failed to serialize": why JSON marshaling errors happen and how to fix them — this error's family across 46 libraries.
Related errors
- The status provided (${init.status}) is not equal to 101 and
- Invalid status text: "${init.statusText}"
- Response with null body status cannot have body
- Invalid redirect status code: ${status}
- Body is unusable
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/9da2a816285d49ee.
Report an issue: GitHub.