Skip to content

t.Nullable() produces non-compliant OpenAPI 3.0 schema with redundant anyOf + nullable #1577

@ohitslaurence

Description

@ohitslaurence

Description

t.Nullable() produces an OpenAPI schema that combines both nullable: true AND anyOf with { type: "null" }, which is redundant and non-compliant with the OpenAPI specification. This causes issues with SDK generators.

Current Behavior

t.Nullable(t.String({ format: "email" }))

Produces:

{
  "nullable": true,
  "anyOf": [
    { "type": "string", "format": "email" },
    { "type": "null" }
  ]
}

Expected Behavior

For OpenAPI 3.0, the correct schema should be:

{
  "type": "string",
  "format": "email",
  "nullable": true
}

Why This Matters

According to the OpenAPI specification:

  • OpenAPI 3.0: Does not support type: "null" directly. You should only use nullable: true as a modifier on the schema.
  • OpenAPI 3.1: Removed nullable entirely in favor of type arrays (type: ["string", "null"]) or anyOf.

The current output is double-specifying nullability, which:

  1. Is non-compliant with OpenAPI 3.0 spec
  2. Confuses SDK generators like @hey-api/openapi-ts (see hey-api/openapi-ts#1160)
  3. Can cause incorrect type generation in client SDKs

Related

Workaround

We're currently post-processing the OpenAPI spec to fix the schema:

function transformNullableSchemas(spec: OpenAPIV3.Document): OpenAPIV3.Document {
  const transform = (obj: unknown): unknown => {
    if (obj === null || typeof obj !== "object") return obj;
    if (Array.isArray(obj)) return obj.map(transform);

    const record = obj as Record<string, unknown>;

    if (
      record.nullable === true &&
      Array.isArray(record.anyOf) &&
      record.anyOf.length === 2
    ) {
      const anyOf = record.anyOf as Array<Record<string, unknown>>;
      const nullType = anyOf.find((item) => item.type === "null");
      const otherType = anyOf.find((item) => item.type !== "null");

      if (nullType && otherType) {
        const { anyOf: _, ...rest } = record;
        return transform({ ...otherType, ...rest, nullable: true });
      }
    }

    const result: Record<string, unknown> = {};
    for (const [key, value] of Object.entries(record)) {
      result[key] = transform(value);
    }
    return result;
  };

  return transform(spec) as OpenAPIV3.Document;
}

Environment

  • Elysia: 1.4.16
  • @elysiajs/openapi: 1.4.11
  • Bun: 1.3.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions