colinhacks/zod · error · Error

NaN cannot be represented in JSON Schema

Error message

NaN cannot be represented in JSON Schema

What it means

Thrown by toJSONSchema() when a ZodNaN schema (z.nan()) is encountered with unrepresentable 'throw' (default). NaN is not a valid JSON number (JSON.parse would never produce it), so JSON Schema cannot represent it.

Solutions

  1. Pass { unrepresentable: 'any' } to toJSONSchema() so the NaN branch becomes {}.
  2. Remove z.nan() from the exported schema; model NaN handling at runtime only.
  3. If the external contract needs a sentinel, use an explicit string literal ('NaN') and translate at the boundary.

Example fix

// before (throws)
const Schema = z.union([z.number(), z.nan()]);
z.toJSONSchema(Schema);

// after
const Schema = z.union([z.number(), z.nan()]);
z.toJSONSchema(Schema, { unrepresentable: 'any' });
// NaN branch collapses to {}; union becomes anyOf [ {type:'number'}, {} ]
Defensive patterns

Strategy: try-catch

Validate before calling

const opts = schemaContains(schema, (s) => s._zod.def.type === 'nan')
  ? { unrepresentable: 'any' }
  : {};
const json = z.toJSONSchema(schema, opts);

Type guard

function hasNaN(schema) {
  return schema._zod.def.type === 'nan';
}

Try / catch

try {
  return z.toJSONSchema(schema);
} catch (e) {
  if (e.message === 'NaN cannot be represented in JSON Schema') {
    return z.toJSONSchema(schema, { unrepresentable: 'any' });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling toJSONSchema() on a schema that includes z.nan(), e.g. as a sentinel in a union. Exporting a model that intentionally admits NaN.

Common situations: Numerical computation schemas that flag NaN results, then exporting the schema for documentation or external validation.

Related errors


AI-assisted analysis of colinhacks/zod@2d90846af9 (2026-08-11). Data as JSON: /api/errors/25a6f2edda964aaa. Report an issue: GitHub.

Appendix: source

Thrown at packages/zod/src/v4/core/json-schema-processors.ts:204

    const val = vals[0]!;
    json.type = val === null ? ("null" as const) : (typeof val as any);
    if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") {
      json.enum = [val];
    } else {
      json.const = val;
    }
  } else {
    if (vals.every((v) => typeof v === "number")) json.type = "number";
    if (vals.every((v) => typeof v === "string")) json.type = "string";
    if (vals.every((v) => typeof v === "boolean")) json.type = "boolean";
    if (vals.every((v) => v === null)) json.type = "null";
    json.enum = vals;
  }
};

export const nanProcessor: Processor<schemas.$ZodNaN> = (_schema, ctx, _json, _params) => {
  if (ctx.unrepresentable === "throw") {
    throw new Error("NaN cannot be represented in JSON Schema");
  }
};

export const templateLiteralProcessor: Processor<schemas.$ZodTemplateLiteral> = (schema, _ctx, json, _params) => {
  const _json = json as JSONSchema.StringSchema;
  const pattern = schema._zod.pattern;
  if (!pattern) throw new Error("Pattern not found in template literal");
  _json.type = "string";
  _json.pattern = pattern.source;
};

export const fileProcessor: Processor<schemas.$ZodFile> = (schema, _ctx, json, _params) => {
  const _json = json as JSONSchema.StringSchema;
  const file: JSONSchema.StringSchema = {
    type: "string",
    format: "binary",
    contentEncoding: "binary",
  };

View on GitHub (pinned to 2d90846af9)