Add support for Go 1.24's `omitzero` to replace unnecessary "optional pointers"
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
goversion that the parentgo.mod- if present - is using, and if 1.24+, useomitzero, 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.
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.
A couple of community contributions so far:
- https://github.com/oapi-codegen/oapi-codegen/pull/1944
(given the number of hearts on the issue I can see this may be something we should look at prioritising in the near future)
The other link does appear to be all about omitempty not omitzero, anyways, yes. That is a bunch of hearts :-)
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 🚀
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!
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"
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
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?
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?
Amazing, thanks. I hadn't seen that addition, should have looked harder