Skip to content

encoding/json: add omitzero option #45669

Closed
tailscale/go
#108
@palsivertsen

Description

@palsivertsen

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Accepted

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions