{"record":{"id":"9da2a816285d49ee","repo":"denoland/deno","slug":"value-is-not-json-serializable","errorCode":null,"errorMessage":"Value is not JSON serializable","messagePattern":"Value is not JSON serializable","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/web/00_infra.js","lineNumber":330,"sourceCode":"/**\n * @param {unknown} cond\n * @param {string=} msg\n * @returns {asserts cond}\n */\nfunction assert(cond, msg = \"Assertion failed.\") {\n  if (!cond) {\n    throw new AssertionError(msg);\n  }\n}\n\n/**\n * @param {unknown} value\n * @returns {string}\n */\nfunction serializeJSValueToJSONString(value) {\n  const result = JSONStringify(value);\n  if (result === undefined) {\n    throw new TypeError(\"Value is not JSON serializable\");\n  }\n  return result;\n}\n\nconst PATHNAME_WIN_RE = new SafeRegExp(/^\\/*([A-Za-z]:)(\\/|$)/);\nconst SLASH_WIN_RE = new SafeRegExp(/\\//g);\nconst PERCENT_RE = new SafeRegExp(/%(?![0-9A-Fa-f]{2})/g);\n\n// Keep in sync with `fromFileUrl()` in `std/path/win32.ts`.\n/**\n * @param {URL} url\n * @returns {string}\n */\nfunction pathFromURLWin32(url) {\n  let p = StringPrototypeReplace(url.pathname, PATHNAME_WIN_RE, \"$1/\");\n  p = StringPrototypeReplace(p, SLASH_WIN_RE, \"\\\\\");\n  p = StringPrototypeReplace(p, PERCENT_RE, \"%25\");\n  let path = decodeURIComponent(p);","sourceCodeStart":312,"sourceCodeEnd":348,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/web/00_infra.js#L312-L348","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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 })"],"exampleFix":"// before\nreturn new Response.json(data.user); // undefined when user missing\n\n// after\nreturn Response.json(data.user ?? null);","handlingStrategy":"type-guard","validationCode":"if (typeof body === \"undefined\" || typeof body === \"function\" || typeof body === \"symbol\") {\n  body = null;\n}\nreturn Response.json(body);","typeGuard":"function isTopLevelJsonSerializable(v: unknown): boolean {\n  return v == null ||\n    (typeof v !== \"function\" && typeof v !== \"symbol\");\n}","tryCatchPattern":"let str: string;\ntry {\n  str = JSON.stringify(value);\n} catch {\n  str = \"{}\"; // circular reference\n}\nif (str === undefined) str = \"null\"; // top-level undefined/function/symbol\nreturn new Response(str, {\n  headers: { \"content-type\": \"application/json\" },\n});","preventionTips":["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"],"tags":["json","serialization","response","fetch","web"],"backgroundTag":"json-serialization-failed","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}