Repository navigation
Description
The omitempty json tag is kind of confusing to use when working with nested structs. The following example illustrates the most basic case using an empty struct for argument's sake.
type Foo struct {
EmptyStruct struct{} `json:",omitempty"`
}The "EmptyStruct" field is a struct without any fields and can be empty, that is equal to it's zero value. But when I try to marshal it to json the field is still included in the resulting json object. Reading the encoding/json documentation about the definition of empty it does not mention empty structs:
The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.
It feels weird that adding the omitempty tag to struct fields is allowed if it will never have the desired effect. If structs will never be considered empty shouldn't there at least be a compiler warning when adding this tag to a struct field?
Working with json time.Time
This behavior causes some confusion when working with time.Time in json structures. Go's zero values for primitive types are fairly reasonable and "guessable" from a non-gopher point of view. But the zero value for time.Time, January 1, year 1, 00:00:00.000000000 UTC, is less common. A more(?) common zero value for time is January 01, 1970 00:00:00 UTC. To avoid confusion when working with json outside the world of go it would be nice to have a way to omit zero value dates.
A commonly suggested workaround to this problem is to use pointers, but pointers might be undesirable for a number of reasons. They are for example cumbersome to assign values to:
type Foo struct {
T *time.Time
}
_ = Foo{
T: &time.Now(), // Compile error
}
_ = Foo{
T: &[]time.Time{time.Now()}[0], // Weird workaround
}The time.Time documentation also recommends to pass the type as value, not pointer since some methods are not concurrency-safe:
Programs using times should typically store and pass them as values, not pointers. That is, time variables and struct fields should be of type time.Time, not *time.Time.
A Time value can be used by multiple goroutines simultaneously except that the methods GobDecode, UnmarshalBinary, UnmarshalJSON and UnmarshalText are not concurrency-safe.
This playground example illustrates three different uses of empty structs where I'd expect the fields to be excluded from the resulting json. Note that the time.Time type has no exposed fields and uses the time.Time.NarshalJSON() function to marshal itself into json.
Solution
Would it make sense to add a new tag, like omitzero, that also excludes structs?
There's a similar proposal in #11939, but that changes the definition of empty in the omitempty documentation.
Activity
mvdan commentedon Apr 21, 2021
Pretty similar to #22480. I think this needs to be put on hold along with #11939. We are aware that the API of encoding/json has issues, and we're working on it.
[-]Omit zero values when encoding json[/-][+]propsoal: encoding/json: add omitzero option[/+][-]propsoal: encoding/json: add omitzero option[/-][+]proposal: encoding/json: add omitzero option[/+]rsc commentedon May 12, 2021
Hold for #22480.
rsc commentedon May 12, 2021
Placed on hold.
— rsc for the proposal review group
rsc commentedon Jun 1, 2022
Taking off hold after closing #52803 as a duplicate of this one.
The big question is if we add ,omitzero do we also add a method to let "am I zero" be user-controllable?
Or perhaps we should allow MarshalJSON to return nil as in #50480.
rsc commentedon Jun 1, 2022
This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group
smikulcik commentedon Jun 1, 2022
Personally I don't like baking the omission policy into struct tags. Things like
omitempty,omitnil,omitzeroare all limited by that one policy. I kinda liked my proposal for #52803 since it allowed that extra flexibility.the
IsEmpty()proposal also adds this flexibility, but it seems odd to further the abstraction of what it means for a property to be "empty". Arn't we just trying to determine if we want to omit a field or not?icholy commentedon Jun 2, 2022
Say we go down that path and I write a type like this:
Now this type can only be used as an
omitzerofield.@rsc
joeshaw commentedon Jun 2, 2022
@icholy I believe the return-nil option would be instead of
omitzero, not in addition to it.icholy commentedon Jun 2, 2022
@joeshaw having a
nilreturn result in anullin nonomitzerooromitemptyfields would change the behavior of existing code.joeshaw commentedon Jun 2, 2022
@icholy Returning
nilwould not result innullin the JSON (that is done by returning[]byte("null")), it would cause the field to be omitted entirely, under the proposal from #50480. Returningniltoday causes invalid JSON to be produced.However, @dsnet points out in #52803 (comment) that
nilas a return value might cause existing code which callsMarshalJSONto break because it is not expecting this behavior.44 remaining items
encoding/json: add omitzero option
encoding/json: add omitzero option
encoding/json: add omitzero option
encoding/json: add omitzero option
encoding/json: add omitzero option
encoding/json: add omitzero option
encoding/json: add omitzero option
cmd: use omitzero option for time.Time
gopherbot commentedon Feb 25, 2025
Change https://go.dev/cl/620595 mentions this issue:
cmd: use omitzero option for time.Time