can1357/oh-my-pi · error · TypeError
Value is not JSON-serializable
Error message
Value is not JSON-serializable
What it means
stableStringifyJson deterministically serializes JSON-shaped data (sorting object keys at every depth, preserving array order). JSON.stringify returns the string undefined when given a value that cannot be a top-level JSON value (undefined, a function, a symbol), so this wrapper converts that silent failure into an explicit TypeError rather than letting callers store/compare the string "undefined".
Source
Thrown at packages/utils/src/json.ts:44
if (Array.isArray(value)) return value.map(stableJsonClone);
if (value !== null && typeof value === "object") {
const sorted = Object.create(null) as Record<string, unknown>;
for (const key of Object.keys(value).sort()) {
sorted[key] = stableJsonClone(Reflect.get(value, key));
}
return sorted;
}
return value;
}
/**
* Deterministically serialize JSON-shaped data by sorting object keys at every
* depth while preserving array order. Throws for values JSON cannot represent
* as a top-level value instead of returning an easy-to-misuse undefined.
*/
export function stableStringifyJson(value: unknown): string {
const serialized = JSON.stringify(stableJsonClone(value));
if (serialized === undefined) throw new TypeError("Value is not JSON-serializable");
return serialized;
}
View on GitHub (pinned to 9690622007)
Solutions
- Check the value before serializing: if (value === undefined) skip or substitute a placeholder like null.
- Strip functions and symbols from the object before passing it (deep clean the data).
- If undefined should serialize, wrap it explicitly, e.g. stableStringifyJson({ value: value ?? null }).
- Catch the TypeError at call sites where the input is genuinely optional.
Example fix
// before const key = stableStringifyJson(options); // throws if options === undefined // after const key = options === undefined ? "null" : stableStringifyJson(options);
Defensive patterns
Strategy: validation
Validate before calling
function isSerializable(v: unknown): boolean {
return v !== undefined && typeof v !== "function" && typeof v !== "symbol";
} Type guard
function isJsonSerializable(v: unknown): v is string | number | boolean | null | object {
return !(v === undefined || typeof v === "function" || typeof v === "symbol");
} Try / catch
try {
return stableStringifyJson(value);
} catch (err) {
if (err instanceof TypeError && err.message === "Value is not JSON-serializable") {
logger.warn("stableStringifyJson got non-serializable value", { type: typeof value });
return "null"; // or a sentinel key
}
throw err;
} Prevention
- Default optional values: value ?? null before serializing.
- Strip functions/symbols from options objects before hashing them into cache keys.
- Don't pass raw option bags with callbacks to serialization helpers — pick the data fields explicitly.
- Unit-test key-building with undefined inputs.
When it happens
Trigger: Calling stableStringifyJson(undefined), stableStringifyJson(() => {...}), stableStringifyJson(Symbol('x')), or an object whose own toJSON returns undefined, e.g. stableStringifyJson({ toJSON: () => undefined }).
Common situations: Passing optional variables that are actually undefined into a cache-key/stable-hash helper, including callback functions in options objects that were meant to be stripped, and class instances with toJSON producing non-serializable values.
Related errors
- RPC payload objects must use string keys
- RPC payload must be JSON-serializable
- Invalid pattern: {err}
- Replacement text is not valid UTF-8: {err}
- V2 compaction stream parse failed: ${err instanceof Error ?
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/3d4a6d9614f0f1c3.
Report an issue: GitHub.