Skip to content

oneOf + top-level nullable: true retains contradictory inferred type: "object" #3928

@nrutman

Description

@nrutman

Is there an existing issue for this?

  • I have searched the existing issues

Current behavior

When a property is decorated with @ApiProperty({ oneOf: [...], nullable: true }), the generated OpenAPI schema retains a contradictory type: "object" alongside the oneOf combinator. The type comes from reflect-metadata, which emits Object for any union type that TypeScript can't represent as a single class (e.g. string | number | null).

Source (latest master / 11.4.3): lib/services/schema-object-factory.ts:319-336

const schemaCombinators = ['oneOf', 'anyOf', 'allOf'];
const declaredSchemaCombinator = schemaCombinators.find(
  (combinator) => combinator in property
);
if (declaredSchemaCombinator) {
  const schemaObjectMetadata = property as SchemaObjectMetadata;

  if (
    schemaObjectMetadata?.type === 'array' ||
    schemaObjectMetadata.isArray
  ) {
    // ...
  } else if (!schemaObjectMetadata['nullable']) {
    delete schemaObjectMetadata.type;
  }
}

When nullable: true, the inferred type is intentionally kept. PR #3784 introduced that retention so allOf could combine with { nullable: true, type: 'X' }. But for oneOf over primitives the retained type: "object" is wrong — the property is not an object, and the resulting schema is unsatisfiable.

This is related to but not the same as #3549 (which was about oneOf with $ref always referencing Object). #3549 was closed by PR #3781, which was reverted same-day in PR #3856. The general "oneOf keeps inferred type" pattern is still present whenever nullable: true is set.

Minimum reproduction code

https://gist.github.com/nrutman/a4b4605164938dbd055040c89976c829

Steps to reproduce

  1. Install @nestjs/swagger@11.4.3 and the deps from the gist.
  2. Decorate a DTO property with oneOf + nullable: true over primitive branches.
  3. Generate the OpenAPI document.
  4. Observe type: "object" alongside the oneOf.

Expected behavior

type inferred from reflect-metadata should be dropped whenever any of oneOf / anyOf / allOf is declared, regardless of nullable. The nullable flag should be preserved on its own (it's documented inline in OAS 3.0 alongside any schema), but it should not gate the deletion of an inferred-only type field.

A possible fix: only retain type when it was explicitly set in the @ApiProperty options. The factory currently doesn't distinguish user-provided type from reflect-metadata-inferred type, so the safest change is to delete type for any of the combinator cases and require the user to re-add it explicitly when they want it (matching pre-nullable-aware behavior).

A user-side workaround that produces a valid schema today is to drop the top-level nullable and push nullable: true into each oneOf branch:

@ApiProperty({
  oneOf: [
    { type: "string", nullable: true },
    { type: "number", nullable: true },
  ],
})
value!: string | number | null;

Package version

11.4.3

NestJS version

11.1.20

Node.js version

22.22.2

In which operating systems have you tested?

  • macOS
  • Windows
  • Linux

Other

Related: #3549, #3274, #3781, #3784, #3856.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions