Skip to content

fix(go): collapse nullable single-member oneOf union to pointer type#2567

Open
siemendev wants to merge 1 commit into
asyncapi:masterfrom
siemendev:fix/go-nullable-oneOf-union-to-pointer
Open

fix(go): collapse nullable single-member oneOf union to pointer type#2567
siemendev wants to merge 1 commit into
asyncapi:masterfrom
siemendev:fix/go-nullable-oneOf-union-to-pointer

Conversation

@siemendev

Copy link
Copy Markdown

Summary

When a JSON Schema uses oneOf: [{type: T}, {type: null}], the conversion layer already does the right thing: it sets isNullable: true on the resulting ConstrainedUnionModel and strips the null member, leaving a single-item union. But the Go generator ignored this and still rendered the union as a named struct type:

// Before — anonymous wrapper, never used anywhere
type NullableString struct {
    string
}

// After — idiomatic Go pointer, used inline where the field is declared
*string

The wrapper type polluted generated output and was functionally useless; consumers had to manually rewrite it to *string/*int/etc.

Changes

src/generators/go/GoConstrainer.ts — Union handler: detect the collapsed-nullable case and return *T:

Union({ constrainedModel }): string {
  // A oneOf:[T, null] schema becomes a single-member nullable union — render as *T.
  if (constrainedModel.options.isNullable && constrainedModel.union.length === 1) {
    return `*${constrainedModel.union[0].type}`;
  }
  return constrainedModel.name;
},

src/generators/go/renderers/UnionRenderer.tsdefaultSelf: return empty string for the same condition so no standalone Go type definition is emitted:

public async defaultSelf(): Promise<string> {
  // oneOf:[T, null] collapses to *T at the property level; no standalone type needed.
  if (this.model.options.isNullable && this.model.union.length === 1) {
    return '';
  }
  // ...

Multi-member nullable unions (e.g. oneOf: [{type: string}, {type: integer}, {type: null}]) are unaffected — they still render as named union structs.

test/generators/go/GoConstrainer.spec.ts — two new Union test cases:

  • 'nullable single-member union renders as pointer to member type'*string
  • 'nullable multi-member union still renders as union type name'NullableMulti (no regression)

Test plan

  • npx jest test/generators/go/GoConstrainer.spec.ts — 2 new tests pass
  • npx jest test/generators/go/ — 69 tests pass, no snapshot changes

Related

Companion to #2566 (fix nullable+required fields emitting value types instead of pointer types). Together these two PRs make Go nullable field generation correct for all combinations.

When a JSON Schema uses `oneOf: [{type: T}, {type: null}]`, the conversion
layer correctly sets `isNullable: true` on the resulting ConstrainedUnionModel
and strips the null member, leaving a single-member union. However, the Go
generator still rendered this as a named struct type (e.g. `type Foo struct
{ string }`), producing dead anonymous wrapper types instead of idiomatic
Go pointer types.

Two-part fix:
- GoConstrainer Union handler: when isNullable=true and union has exactly
  one member, return `*T` (pointer to the member type) instead of the union
  type name. Multi-member nullable unions are unaffected.
- UnionRenderer defaultSelf: return empty string for the same condition so
  no standalone Go type definition is emitted for the collapsed union.

Added two new test cases in GoConstrainer.spec.ts:
- nullable single-member union → `*string`
- nullable multi-member union → still uses union type name (no regression)

Related: fixes the same class of nullable-type issues as
asyncapi#2566
@netlify

netlify Bot commented May 28, 2026

Copy link
Copy Markdown

Deploy Preview for modelina canceled.

Name Link
🔨 Latest commit 821e4cb
🔍 Latest deploy log https://app.netlify.com/projects/modelina/deploys/6a17e43c2421e90008021b66

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant