-
Notifications
You must be signed in to change notification settings - Fork 17.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
encoding/json: add omitzero option #45669
Comments
Hold for #22480. |
Placed on hold. |
This proposal has been added to the active column of the proposals project |
Personally I don't like baking the omission policy into struct tags. Things like the |
Say we go down that path and I write a type like this: type Null[T any] struct {
Valid bool
Value T
}
func (n Null[T]) MarshalJSON() ([]byte, error) {
if !n.Valid {
return nil, nil
}
return json.Marshal(n.Valid)
} Now this type can only be used as an |
@icholy I believe the return-nil option would be instead of |
@joeshaw having a |
@icholy Returning However, @dsnet points out in #52803 (comment) that |
@joeshaw I want to have a general purpose |
@icholy So you return string |
@mitar my |
True. I think this is a broader issue: |
Bugs in 2 and 3 can be fixed as follows: // fast path omitEmpty
if f.omitEmpty && isEmptyValue(fv) {
continue
}
omitEmptyResetLocation := e.Len()
e.WriteByte(next)
if opts.escapeHTML {
e.WriteString(f.nameEscHTML)
} else {
e.WriteString(f.nameNonEsc)
}
opts.quoted = f.quoted
startLen := e.Len()
f.encoder(e, fv, opts)
newLen := e.Len()
if f.omitEmpty && (newLen-startLen) <= 5 {
// using `Next` and `bytes.NewBuffer` we can modify the end of the
// underlying slice efficiently (without doing any copying)
fullBuf := e.Next(newLen) // extract underlying slice from buffer
switch string(fullBuf[startLen:newLen]) {
case "false", "0", "\"\"", "null", "[]":
// reconstruct buffer without zero value
e.Buffer = *bytes.NewBuffer(fullBuf[:omitEmptyResetLocation])
continue
default:
// reconstruct original buffer
e.Buffer = *bytes.NewBuffer(fullBuf)
}
}
next = ',' Feature 4 can be added as follows: // Emptyable is the interface implemented by types that
// can provide a function to determine if they are empty.
type Emptyable interface {
IsEmpty() bool
}
var emptyableType = reflect.TypeOf((*Emptyable)(nil)).Elem()
func isEmptyValue(v reflect.Value) bool {
// first check via the type if `Emptyable` is implemented
// this way, we can prevent the more expensive `Interface`
// conversion if not required
if v.Type().Implements(emptyableType) {
if v, ok := v.Interface().(Emptyable); ok {
return v.IsEmpty()
}
}
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
return false
} NOTE: Instead of adding a NOTE2 (edit): by replacing @joeshaw I tried to make this analysis as complete as possible. Please let me know in case I missed something. |
This commit adds the `IsZero() bool` method to the `Identity` type. It returns true if the identity is equal to the Identity zero value. A 'valid' identity won't be zero since `H(data)` will not produce a hash value of all zero bits with overwhelming probability if a H is a collision-resistant hash function. The `IsZero` method is inline with the accepted Go JSON proposal adding the struct tag `omitzero`. Ref: golang/go#45669 (comment) Signed-off-by: Andreas Auernhammer <[email protected]>
Fixes golang#45669 Change-Id: Idec483a03968cc671c8da27804589008b10864a1
Fixes golang#45669 Change-Id: Idec483a03968cc671c8da27804589008b10864a1
Change https://go.dev/cl/615676 mentions this issue: |
Fixes golang#45669 Change-Id: Idec483a03968cc671c8da27804589008b10864a1
Fixes golang#45669 Change-Id: Idec483a03968cc671c8da27804589008b10864a1
Fixes golang#45669 Change-Id: Idec483a03968cc671c8da27804589008b10864a1
Fixes golang#45669 Change-Id: Idec483a03968cc671c8da27804589008b10864a1
Fixes golang#45669 Change-Id: Idec483a03968cc671c8da27804589008b10864a1
Fixes golang#45669 Change-Id: Idec483a03968cc671c8da27804589008b10864a1
Fixes golang#45669 Change-Id: Ic13523c0b3acdfc5b3e29a717bc62fde302ed8fd GitHub-Last-Rev: 57030f2 GitHub-Pull-Request: golang#69622 Reviewed-on: https://go-review.googlesource.com/c/go/+/615676 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Reviewed-by: Joseph Tsai <[email protected]> Reviewed-by: Michael Knyszek <[email protected]>
Fixes golang#45669 Change-Id: Ic13523c0b3acdfc5b3e29a717bc62fde302ed8fd GitHub-Last-Rev: 57030f2 GitHub-Pull-Request: golang#69622 Reviewed-on: https://go-review.googlesource.com/c/go/+/615676 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Reviewed-by: Joseph Tsai <[email protected]> Reviewed-by: Michael Knyszek <[email protected]>
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.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:
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 fortime.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:
The
time.Time
documentation also recommends to pass the type as value, not pointer since some methods 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 thetime.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.The text was updated successfully, but these errors were encountered: