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

  1. Pass { cycles: "ref" } to handle recursive schemas via $defs/$ref instead of embedded cycles.
  2. Audit custom override/processor code and .meta() for non-serializable values (functions, Symbols, bigints).
  3. Reproduce locally and inspect `result` just before the clone to locate the offending property.
  4. 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

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


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)