colinhacks/zod · error · Error
fromJSONSchema input is not valid JSON (possibly cyclic); us
Error message
fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas
What it means
Thrown when `JSON.parse(JSON.stringify(schema))` fails inside fromJSONSchema. The round-trip normalises the input into a plain finite object graph; cyclic object graphs (and other non-JSON-serialisable inputs like BigInt, functions, or symbols) fail here. The error message explicitly points to `$defs/$ref` as the supported way to express recursion.
Source
Thrown at packages/zod/src/v4/classic/from-json-schema.ts:643
}
/**
* Converts a JSON Schema to a Zod schema. This function should be considered semi-experimental. It's behavior is liable to change. */
export function fromJSONSchema(schema: JSONSchema.JSONSchema | boolean, params?: FromJSONSchemaParams): ZodType {
// Handle boolean schemas
if (typeof schema === "boolean") {
return schema ? z.any() : z.never();
}
// Normalize input via a JSON round-trip. This guarantees the converter
// walks a plain, finite, JSON-valid object graph: cyclic inputs fail here,
// getter/Proxy-based properties are materialized into static values, and
// class instances collapse to plain objects.
let normalized: JSONSchema.JSONSchema;
try {
normalized = JSON.parse(JSON.stringify(schema));
} catch {
throw new Error("fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas");
}
const version = detectVersion(normalized, params?.defaultTarget);
const defs = (normalized.$defs || normalized.definitions || {}) as Record<string, JSONSchema.JSONSchema>;
const ctx: ConversionContext = {
version,
defs,
refs: new Map(),
processing: new Set(),
rootSchema: normalized,
registry: params?.registry ?? globalRegistry,
};
return convertSchema(normalized, ctx);
}
View on GitHub (pinned to 912f0f51b0)
Solutions
- Express recursion declaratively: hoist the recursive shape into `$defs` and reference it with `#/$defs/Name`.
- Remove runtime cycles from the input object before passing it in (deep clone with a cycle-breaking library if needed).
- Strip non-JSON values (BigInt, functions, symbols) before calling fromJSONSchema.
Example fix
// before (runtime cycle)
const node = { type: "object", properties: {} };
node.properties.self = node; // cycle
z4.fromJSONSchema(node); // throws
// after (declarative recursion via $defs)
const schema = {
$defs: {
Node: {
type: "object",
properties: { self: { $ref: "#/$defs/Node" } }
}
},
$ref: "#/$defs/Node"
};
z4.fromJSONSchema(schema); Defensive patterns
Strategy: validation
Validate before calling
function assertJsonable(schema: unknown) {
JSON.stringify(schema); // throws on cycles / non-JSON values
} Type guard
function isPlainJson(v: any, seen = new WeakSet()): boolean {
if (v === null || typeof v !== "object") return ["string","number","boolean"].includes(typeof v) || v == null;
if (seen.has(v)) return false;
seen.add(v);
return Array.isArray(v) ? v.every((x) => isPlainJson(x, seen)) : Object.values(v).every((x) => isPlainJson(x, seen));
} Try / catch
try { const s = z4.fromJSONSchema(schema); }
catch (e) {
if (e instanceof Error && /not valid JSON|cyclic/.test(e.message)) {
// hoist recursive shape into $defs/$ref and retry
}
throw e;
} Prevention
- Never build schema graphs with runtime cycles; use `$defs` + `$ref` for recursion.
- Strip non-JSON values (BigInt, functions, symbols) before calling fromJSONSchema.
- Validate input with JSON.stringify in tests to surface cycles early.
When it happens
Trigger: Passing a JavaScript object that contains a real cycle (e.g. `a.self = a`) instead of representing recursion declaratively via `$defs` and `$ref`. Also triggered by non-JSON values such as BigInt keys, functions, or Proxies that throw during serialisation.
Common situations: Building the input schema programmatically and linking nodes by reference; converting class instances with circular back-pointers; deserialised JSON patched in memory to add cycles.
Related errors
- Circular reference not resolved: ${refPath}
- External $ref is not supported, only local refs (#/...) are
- Reference not found: ${ref}
- not is not supported in Zod (except { not: {} } for never)
- unevaluatedItems is not supported
AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03).
Data as JSON: /data/errors/b8dca60b88fe8d23.json.
Report an issue: GitHub.