oapi-codegen icon indicating copy to clipboard operation
oapi-codegen copied to clipboard

Add support for Go 1.24's `omitzero` to replace unnecessary "optional pointers"

Open jamietanna opened this issue 10 months ago • 4 comments

Problem

Due to the way that Go's JSON marshalling works, the standard way to make a field not marshal, if it was not specified, was to use a pointer, and a JSON tag of omitempty:

type Response struct {
	UpdatedAt *time.Time `json:"updated_at,omitempty"`
}

This is the default behaviour within oapi-codegen, and the expectation within the Go community, but it can be unwieldy.

Over the years, we've had a number of requests to make this easier to work with, in particular for folks who don't want to be dealing with pointers to things, or where there are i.e. pointers to slices:

  • https://github.com/oapi-codegen/oapi-codegen/discussions/1460
  • https://github.com/oapi-codegen/oapi-codegen/pull/1694
  • https://github.com/oapi-codegen/oapi-codegen/issues/1561

(not exhaustive)

We support the field-level extension, x-go-type-skip-optional-pointer, on fields within a spec, such as:

    ClientWithExtension:
      type: object
      required:
        - name
      properties:
        name:
          type: string
        id:
          type: number
          x-go-type-skip-optional-pointer: true

But this gets repetitive.

Solution

However, with the upcoming Go 1.24 release, this is something that can now handle with the new changes to encoding/json which introduces omitzero (proposal)

This would allow us to no longer provide a pointer for a type that is optional, when we can instead omitzero the zero value.

What about previous versions of Go?

This would only be intended for use with Go 1.24+ codebases.

Does this require everyone upgrade to Go 1.24?

No, the intent is that we either:

  • make this an opt-in flag, via output-options
  • auto-detect the go version that the parent go.mod - if present - is using, and if 1.24+, use omitzero, rather than optional pointers

Out of scope

Additionally - and not part of this proposal - is that there are performance implications where you may want to use a pointer for a type, or may not.

jamietanna avatar Feb 11 '25 21:02 jamietanna

The default pointer behavior has non-Go-idiomatic behaviors like pointers to maps and slices (#1561). Go's JSON marshaling does not require pointers for such cases.

Client code needs to be written with wrappers around every read and write, which is error prone and cumbersome. Checking for nil pointers doesn't obviate needing to check the dereferenced values, so it doesn't add value in most cases.

We're trying x-go-type-skip-optional-pointer, but making it easier to produce that behavior seems like a good idea.

bgrant0607 avatar Apr 04 '25 19:04 bgrant0607

A couple of community contributions so far:

  • https://github.com/oapi-codegen/oapi-codegen/pull/1944

jamietanna avatar Apr 07 '25 14:04 jamietanna

(given the number of hearts on the issue I can see this may be something we should look at prioritising in the near future)

jamietanna avatar Apr 07 '25 14:04 jamietanna

The other link does appear to be all about omitempty not omitzero, anyways, yes. That is a bunch of hearts :-)

lzap avatar Apr 17 '25 13:04 lzap

This will be going in later today - need to do some small tweaks to our README before I can finish off merging in @lzap's great contribution 🚀

jamietanna avatar Jul 15 '25 08:07 jamietanna

I think I've unfortunately closed this in a bit of a hasty nature - re-reading the intent here, it's not quite solved by adding the support for x-omitzero, sorry folks!

jamietanna avatar Jul 15 '25 08:07 jamietanna

This looks like it'll be very similar to https://github.com/oapi-codegen/oapi-codegen/pull/1694, with the addition of "and make sure that omitzero is added to the JSON tags"

jamietanna avatar Jul 15 '25 08:07 jamietanna

This will be supported by https://github.com/oapi-codegen/oapi-codegen/pull/2023 - any feedback welcome, but I'll be looking at merging it today

jamietanna avatar Jul 15 '25 12:07 jamietanna

Hi @jamietanna I was under the impression that this would be geared towards allowing users to apply this feature to the entire spec instead of on a per field basis. This latest change still appears to require setting it on all fields individually unless I'm mistaken?

mminklet avatar Sep 10 '25 06:09 mminklet

Have you taken a look at https://github.com/oapi-codegen/oapi-codegen?tab=readme-ov-file#globally-skipping-the-optional-pointer which indicates the global setting prefer-skip-optional-pointer-with-omitzero which should do what you want?

jamietanna avatar Sep 10 '25 06:09 jamietanna

Amazing, thanks. I hadn't seen that addition, should have looked harder

mminklet avatar Sep 10 '25 09:09 mminklet