Skip to content

proposal: encoding/json, encoding/xml: support zero values of structs with omitempty #11939

Not planned
kubernetes/test-infra
#12414
@joeshaw

Description

@joeshaw

Support zero values of structs with omitempty in encoding/json and encoding/xml.

This bites people a lot, especially with time.Time. Open bugs include #4357 (which has many dups) and #10648. There may be others.

Proposal

Check for zero struct values by adding an additional case to the isEmptyValue function:

case reflect.Struct:
        return reflect.Zero(v.Type()).Interface() == v.Interface()

This will solve the vast majority of cases.

(Optional) Introduce a new encoding.IsZeroer interface, and use this to check for emptiness:

Update: I am dropping this part of the proposal, see below.

type IsZeroer interface {
        IsZero() bool
}

Visit this playground link and note that the unmarshaled time.Time value does not have a nil Location field. This prevents the reflection-based emptiness check from working. IsZero() already exists on time.Time, has the correct semantics, and has been adopted as a convention by Go code outside the standard library.

An additional check can be added to the isEmptyValue() functions before checking the value's Kind:

if z, ok := v.Interface().(encoding.IsZeroer); ok {
        return z.IsZero()
}

Compatibility

The encoding.IsZeroer interface could introduce issues with existing non-struct types that may have implemented IsZero() without consideration of omitempty. If this is undesirable, the encoding.IsZeroer interface check could be moved only within the struct case:

case reflect.Struct:
        val := v.Interface()
        if z, ok := val.(encoding.IsZeroer); ok {
                return z.IsZero()
        }
        return reflect.Zero(v.Type()).Interface() == val

Otherwise, this change is backward-compatible with existing valid uses of omitempty. Users who have applied omitempty to struct fields incorrectly will get their originally intended behavior for free.

Implementation

I (@joeshaw) have implemented and tested this change locally, and will send the CL when the Go 1.6 tree opens.

Activity

added this to the Unplanned milestone on Jul 30, 2015
gopherbot

gopherbot commented on Aug 25, 2015

@gopherbot
Contributor

CL https://golang.org/cl/13914 mentions this issue.

gopherbot

gopherbot commented on Aug 28, 2015

@gopherbot
Contributor

CL https://golang.org/cl/13977 mentions this issue.

joeshaw

joeshaw commented on Sep 18, 2015

@joeshaw
ContributorAuthor

The empty struct approach is implemented in CL 13914 and the IsZeroer interface is implemented in CL 13977.

In order for them to be reviewable separately they conflict a bit -- mostly in the documentation -- but I will fix for one if the other is merged.

joeshaw

joeshaw commented on Oct 19, 2015

@joeshaw
ContributorAuthor

In the CLs @rsc said,

I'd really like to stop adding to these packages. I think we need to leave well enough alone at some point.

I see what he's getting at. CL 13977, which implements the IsZeroer interface is clearly an enhancement and adds API to the standard library that needs to be maintained forever. So, I am abandoning that CL and that part of the proposal.

However, I still feel strongly about omitempty with empty structs, and I want to push for CL 13914 to land for Go 1.6.

I use the JSON encoding in Go a lot, as my work is mostly writing services that communicate with other services, in multiple languages, over HTTP. The fact that structs don't obey omitempty is a frequent source of confusion (see #4357 and its many dups and references, and #10648) and working around it is really annoying. Other programming languages do not conform to Go's ideal "zero value" idea, and as a result encoding a zero value is semantically different in JSON than omitting it or encoding it as null. People run into this most commonly with time.Time. (There is also the issue that decoding a zero time.Time does not result in an empty struct, see #4357 (comment) for background on that.)

I think it should be considered a bug that Go does not support omitempty for these types, and although it adds a small amount of additional code, it fixes a bug.

modified the milestones: Proposal, Unplanned on Oct 24, 2015
changed the title [-]proposal: encoding: Support zero values of structs with omitempty in encoding/json and encoding/xml[/-] [+]proposal: encoding/json, encoding/xml: support zero values of structs with omitempty[/+] on Oct 24, 2015
jeromenerf

jeromenerf commented on Mar 27, 2016

@jeromenerf

This proposal is marked as unplanned, yet the related bug report #10648 is marked as go1.7.
Is it still being worked /thought on?

rsc

rsc commented on Mar 28, 2016

@rsc
Contributor

To my knowledge, it is not being worked on. Honestly this seems fairly low
priority and will likely miss Go 1.7.

On Sun, Mar 27, 2016 at 12:22 PM Jérôme Andrieux notifications@github.com
wrote:

This proposal is marked as unplanned, yet the related bug report #10648
#10648 is marked as go1.7.
Is it still being worked /thought on?


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#11939 (comment)

jeromenerf

jeromenerf commented on Mar 28, 2016

@jeromenerf

OK.

This is more of a convenience than a priority indeed.

It can be a pain point when dealing with libs that don't support "embedded structs" as pointer though.

Perelandric

Perelandric commented on Mar 29, 2016

@Perelandric

I wonder if a low-impact alternative to the IsZeroer interface would be to allow one to return an error called json.CanOmit (or similar) from an implementation of the Marshaler interface. That way the dev is in control of determining what constitutes a zero value, and it doesn't impact other code.

It's not a perfect solution, since one can't add methods to types defined in another package, but this can be worked around to a degree.

Taking the time.Time example:

type MyTime struct {
  time.Time
}

// Implement the Marshaler interface
func (mt MyTime) MarshalJSON() ([]byte, error) {
  res, err := json.Marshal(mt.Time)

  if err == nil && mt.IsZero() {
    return res, json.CanOmit // Exclude zero value from fields with `omitempty`
  }
  return res, err
}

I haven't looked into implementation, but on the surface it would seem like a low-overhead solution, assuming the only work would be to check if an error returned equals json.CanOmit on fields where omitempty was included.

Using errors as a flag is not without precedent in the standard library, e.g. filepath#WalkFunc allows one to return filepath.SkipDir to skip recursion into a directory.

242 remaining items

ianlancetaylor

ianlancetaylor commented on Sep 10, 2024

@ianlancetaylor
Contributor

Thanks, I agree that the existing proposal is sufficient here.

nyetwurk

nyetwurk commented on Mar 13, 2025

@nyetwurk

To clarify, will omitzero omit structs that are empty?

ianlancetaylor

ianlancetaylor commented on Mar 13, 2025

@ianlancetaylor
Contributor

@nyetwurk What precisely do you mean by empty? The omitzero tag, which is in Go 1.24, will omit a struct if the struct value is the zero value of its type.

nyetwurk

nyetwurk commented on Mar 13, 2025

@nyetwurk

Specifically, if *all *members of a struct are omitted, due to omitempty or omitzero, and would otherwise be rendered as
{} or {{},{},..} etc.

ianlancetaylor

ianlancetaylor commented on Mar 13, 2025

@ianlancetaylor
Contributor

The omitempty tag has no effect on struct types. The omitzero tag will cause the entire struct to be omitted if it has the zero value of its type. It won't be represented as {} or whatever, it will simply be skipped.

If you have a struct, and every individual field are marked as omitempty or omitzero, and is omitted, then the struct will be displayed as {} or something but the fields will be omitted.

That is, it depends on whether the tag is on the fields of a struct or on a field of struct type.

nyetwurk

nyetwurk commented on Mar 13, 2025

@nyetwurk

https://go-review.googlesource.com/c/go/+/13914 has been abandoned and #51261 will just issue a warning

mitar

mitar commented on Mar 13, 2025

@mitar
Contributor

@nyetwurk I think you want to look at #45669.

nyetwurk

nyetwurk commented on Mar 13, 2025

@nyetwurk

From what I can tell, #45669 requires the struct to implement IsZero for omitzero to omit it... though I can't tell for sure.

Will structs like {} be omitted on omitzero if they do not implement an IsZero?

sagikazarmark

sagikazarmark commented on Mar 13, 2025

@sagikazarmark
nyetwurk

nyetwurk commented on Mar 14, 2025

@nyetwurk

I see now. This has the behavior I want
https://go.dev/play/p/DE5SRi8LBtL
https://go.dev/play/p/xVLdqu5JcUZ

Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      Participants

      @bradfitz@nathany@dsymonds@Dynom@joeshaw

      Issue actions

        proposal: encoding/json, encoding/xml: support zero values of structs with omitempty · Issue #11939 · golang/go