colinhacks/zod · error · Error
Error converting schema to JSON.
Error message
Error converting schema to JSON.
What it means
Thrown at the very end of z.toJSONSchema() when JSON.parse(JSON.stringify(result)) throws while finalizing the generated schema. The deep-clone is used to guarantee the output has no cycles and no non-serializable values; any residual circular reference, function/Symbol in the result, or bigint/Date that escaped the processors will cause JSON.stringify to fail, which the catch wraps in a generic message.
Solutions
- Pass { cycles: "ref" } to handle recursive schemas via $defs/$ref instead of embedded cycles.
- Audit custom override/processor code and .meta() for non-serializable values (functions, Symbols, bigints).
- Reproduce locally and inspect `result` just before the clone to locate the offending property.
- Strip or stringify metadata values before registering them on schemas.
Example fix
// before — recursive schema without cycles option, or meta with a function
const Tree = z.lazy(() => z.object({ kids: z.array(Tree) }));
const json = z.toJSONSchema(Tree);
// after
const json = z.toJSONSchema(Tree, { cycles: "ref" }); Defensive patterns
Strategy: try-catch
Validate before calling
function isSerializable(v: unknown): boolean {
try { JSON.stringify(v); return true; } catch { return false; }
} Type guard
function isSerializable(v: unknown): boolean {
try { JSON.stringify(v); return true; } catch { return false; }
} Try / catch
try {
return z.toJSONSchema(schema);
} catch (e) {
if (/Error converting schema to JSON/.test((e as Error).message)) {
return z.toJSONSchema(schema, { cycles: "ref" });
}
throw e;
} Prevention
- Always pass { cycles: "ref" } for recursive schemas.
- Keep .meta() values JSON-serializable (no functions/Symbols/bigints).
- Audit custom override/processor callbacks for non-serializable mutations.
- Reproduce locally and inspect the pre-clone `result` to find the offending property.
When it happens
Trigger: A schema that, after processing, yields a result object JSON.stringify cannot serialize — most often a residual circular reference that wasn't converted to $ref (e.g. cycles option mishandled, or a custom processor that reintroduces a cycle), or a custom schema type that injected a function/bigint/Symbol into the JSON Schema output. Also reachable if a metadata override attached a non-serializable value.
Common situations: Custom processors or override callbacks that attach non-JSON values; recursive schemas converted without cycles:"ref" that slipped past the earlier cycle check (e.g. a cycle introduced during processing rather than in the input graph); .meta() carrying function-valued keys; third-party plugins that mutate the result.
Related errors
- Cycle detected: #/ / Set the `cycles` parameter to `"ref"`…
- Duplicate schema id " " detected during JSON Schema…
- Circular reference not resolved
- Conditional schemas (if/then/else) are not supported
- dependentSchemas and dependentRequired are not supported
AI-assisted analysis of colinhacks/zod@2d90846af9 (2026-08-11).
Data as JSON: /api/errors/53c242ee4c5353cc.
Report an issue: GitHub.
Appendix: source
Thrown at packages/zod/src/v4/core/to-json-schema.ts:523
// this "finalizes" this schema and ensures all cycles are removed
// each call to finalize() is functionally independent
// though the seen map is shared
const finalized = JSON.parse(JSON.stringify(result));
Object.defineProperty(finalized, "~standard", {
value: {
...schema["~standard"],
jsonSchema: {
input: createStandardJSONSchemaMethod(schema, "input", ctx.processors),
output: createStandardJSONSchemaMethod(schema, "output", ctx.processors),
},
},
enumerable: false,
writable: false,
});
return finalized;
} catch (_err) {
throw new Error("Error converting schema to JSON.");
}
}
function isTransforming(
_schema: schemas.$ZodType,
_ctx?: {
seen: Set<schemas.$ZodType>;
}
): boolean {
const ctx = _ctx ?? { seen: new Set() };
if (ctx.seen.has(_schema)) return false;
ctx.seen.add(_schema);
const def = (_schema as schemas.$ZodTypes)._zod.def;
if (def.type === "transform") return true;
View on GitHub (pinned to 2d90846af9)