OpenAPITools / OpenAPITools/openapi-generator

[REQ] [Go] Nullable* types should be easier to consume

Open
#8,340 5 comments 8 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Enhancement: Feature
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

Is your feature request related to a problem? Please describe.

openapi-generator 5.x introduced new Nullable types in the Golang code generator.

While "proper" nullables are useful, it's not entirely clear why they were implemented with pointer New()/Get()/Set() arguments and an additional isSet flag, instead of supporting plain values and using the nil pointer as a "is not set" marker.

Describe the solution you'd like

The Nullable* types should be replaced with pointer to type wrappers, New() and Set() should take a plain value and Get() should return a plain value.

Describe alternatives you've considered

The alternative is to use additional logic to check for nil values on every access to the Nullables. When creating a Nullable from a literal, it is necessary to create an intermediate variable first.

Additional context

With the current implementation, one has to do the following:

// works
s1 := "test"
ns1 := NewNullableString(&s1)
// does not work (can't construct a pointer to string literal)
ns2 := NewNullableString(&"test")
// works
var s3 string
if ns3.isSet() {
    v3 := ns3.Get()
    if v3 != nil {
        s3 = *v3
    }
}
// works
var s4 string
if ns4.isSet() && ns4.Get() != nil {
    s4 = *ns4.Get()
}
// works, sets isSet to true, but the pointer to nil
ns5 := NewNullableString(nil)
// does not work, nil pointer dereference
ns6 := NewNullableString(nil)
var s6 string
if ns6.IsSet() {
    s6 = *ns6.Get()
}
// works, sets isSet to false, but the pointer to non-nil (which doesn't make much sense)
s7 := "test"
ns7 := NullableString{&s7, false}

By getting rid of the isSet flag, using a wrapping type and replacing the New()/Get()/Set() functions with by-value versions, usage is more convenient:

type NullableString struct {
    *string
}

func (v NullableString) Get() string {
    if v.string != nil {
        return *v.string
    }
    return ""
}

func (v *NullableString) Set(val string) {
	v.string = &val
}

func (v NullableString) IsSet() bool {
	return v.string != nil
}

func (v *NullableString) Unset() {
	v.string = nil
}

func NewNullableString(val string) NullableString {
	return NullableString{&val}
}

func (v NullableString) MarshalJSON() ([]byte, error) {
	return json.Marshal(v.string)
}

func (v *NullableString) UnmarshalJSON(src []byte) error {
	return json.Unmarshal(src, v.string)
}

Usage would then be like this:

// works
s1 := "test"
ns1 := NewNullableString(s1)
if !ns1.IsSet() {
    panic("")
}
// works
ns2 := NewNullableString("test")
if !ns2.IsSet() {
    panic("")
}
// works
s3 := "test"
ns3 := NullableString{&s3}
if !ns3.IsSet() {
    panic("")
}
// works
ns4 := NewNullableString("test")
var s4 string
if ns4.IsSet() {
    s4 = ns4.Get()
} else {
    panic("")
}
if s4 != "test" {
    panic("")
}
// works
ns5 := NewNullableString("test")
s5 := *ns5.string
if s5 != "test" {
    panic("")
}
// works
ns6 := NullableString{nil}
if ns6.IsSet() {
    panic("nil value should be unset")
}
// works, s7 is the empty string
ns7 := NullableString{nil}
s7 := ns7.Get()
if s7 != "" {
    panic("")
}
// works
ns8 := NewNullableString("test")
ns8.MarshalJSON()
// works, s9 is the empty string
ns9 := NewNullableString("test")
ns9.Unset()
if ns9.IsSet() {
    panic("")
}
s9 := ns9.Get()
if s9 != "" {
    panic("")
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the Go code generator's Nullable* implementation and trace how New(), Get(), Set(), IsSet(), Unset(), MarshalJSON(), and UnmarshalJSON() are generated. Compare the current behavior with the proposed pointer-wrapper design, including literal construction and nil handling. Done means the generated nullable types use by-value accessors without an isSet flag and preserve the intended JSON behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.