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

  1. Check the value before serializing: if (value === undefined) skip or substitute a placeholder like null.
  2. Strip functions and symbols from the object before passing it (deep clean the data).
  3. If undefined should serialize, wrap it explicitly, e.g. stableStringifyJson({ value: value ?? null }).
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/3d4a6d9614f0f1c3. Report an issue: GitHub.