Skip to content

Commit 903dea1

Browse files
committed
encoding/json: add omitzero option
Fixes #45669 Change-Id: Idec483a03968cc671c8da27804589008b10864a1
1 parent 8014360 commit 903dea1

File tree

3 files changed

+171
-3
lines changed

3 files changed

+171
-3
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
When marshaling, a struct field with the new `omitzero` option in the struct field
2+
tag will be omitted if its value is zero. If the field type has an `IsZero() bool`
3+
method, that will be used to determine whether the value is zero. Otherwise, the
4+
value is zero if it is [the zero value for its type](/ref/spec#The_zero_value).
5+
6+
If both `omitempty` and `omitzero` are specified, the field will be omitted if the
7+
value is either empty or zero (or both).

src/encoding/json/encode.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,32 @@ import (
9999
// // Field appears in JSON as key "-".
100100
// Field int `json:"-,"`
101101
//
102+
// The "omitzero" option specifies that the field should be omitted
103+
// from the encoding if the field has a zero value, according to:
104+
//
105+
// 1) If the field type has an "IsZero() bool" method, that will be used to
106+
// determine whether the value is zero.
107+
//
108+
// 2) Otherwise, the value is zero if it is the zero value for its type.
109+
//
110+
// Examples of struct field tags and their meanings:
111+
//
112+
// // Field appears in JSON as key "myName".
113+
// Field time.Time `json:"myName"`
114+
//
115+
// // Field appears in JSON as key "myName" and
116+
// // the field is omitted from the object if its value is zero,
117+
// // as defined above.
118+
// Field time.Time `json:"myName,omitzero"`
119+
//
120+
// // Field appears in JSON as key "Field" (the default), but
121+
// // the field is skipped if zero.
122+
// // Note the leading comma.
123+
// Field time.Time `json:",omitzero"`
124+
//
125+
// If both "omitempty" and "omitzero" are specified, the field will be omitted
126+
// if the value is either empty or zero (or both).
127+
//
102128
// The "string" option signals that a field is stored as JSON inside a
103129
// JSON-encoded string. It applies only to fields of string, floating point,
104130
// integer, or boolean types. This extra level of encoding is sometimes used
@@ -318,6 +344,15 @@ func isEmptyValue(v reflect.Value) bool {
318344
return false
319345
}
320346

347+
func isZeroValue(v reflect.Value) bool {
348+
if z, ok := v.Interface().(interface {
349+
IsZero() bool
350+
}); ok {
351+
return z.IsZero()
352+
}
353+
return v.IsZero()
354+
}
355+
321356
func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) {
322357
valueEncoder(v)(e, v, opts)
323358
}
@@ -701,7 +736,8 @@ FieldLoop:
701736
fv = fv.Field(i)
702737
}
703738

704-
if f.omitEmpty && isEmptyValue(fv) {
739+
if (f.omitEmpty && isEmptyValue(fv)) ||
740+
(f.omitZero && isZeroValue(fv)) {
705741
continue
706742
}
707743
e.WriteByte(next)
@@ -1048,6 +1084,7 @@ type field struct {
10481084
index []int
10491085
typ reflect.Type
10501086
omitEmpty bool
1087+
omitZero bool
10511088
quoted bool
10521089

10531090
encoder encoderFunc
@@ -1154,6 +1191,7 @@ func typeFields(t reflect.Type) structFields {
11541191
index: index,
11551192
typ: ft,
11561193
omitEmpty: opts.Contains("omitempty"),
1194+
omitZero: opts.Contains("omitzero"),
11571195
quoted: quoted,
11581196
}
11591197
field.nameBytes = []byte(field.name)

src/encoding/json/encode_test.go

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ import (
1515
"runtime/debug"
1616
"strconv"
1717
"testing"
18+
"time"
1819
)
1920

20-
type Optionals struct {
21+
type OptionalsEmpty struct {
2122
Sr string `json:"sr"`
2223
So string `json:"so,omitempty"`
2324
Sw string `json:"-"`
@@ -56,7 +57,7 @@ func TestOmitEmpty(t *testing.T) {
5657
"str": {},
5758
"sto": {}
5859
}`
59-
var o Optionals
60+
var o OptionalsEmpty
6061
o.Sw = "something"
6162
o.Mr = map[string]any{}
6263
o.Mo = map[string]any{}
@@ -70,6 +71,128 @@ func TestOmitEmpty(t *testing.T) {
7071
}
7172
}
7273

74+
type NonZeroStruct struct{}
75+
76+
func (nzs NonZeroStruct) IsZero() bool {
77+
return false
78+
}
79+
80+
type OptionalsZero struct {
81+
Sr string `json:"sr"`
82+
So string `json:"so,omitzero"`
83+
Sw string `json:"-"`
84+
85+
Ir int `json:"omitzero"` // actually named omitzero, not an option
86+
Io int `json:"io,omitzero"`
87+
88+
Slr []string `json:"slr,random"`
89+
Slo []string `json:"slo,omitzero"`
90+
SloNonNil []string `json:"slononnil,omitzero"`
91+
92+
Mr map[string]any `json:"mr"`
93+
Mo map[string]any `json:",omitzero"`
94+
95+
Fr float64 `json:"fr"`
96+
Fo float64 `json:"fo,omitzero"`
97+
98+
Br bool `json:"br"`
99+
Bo bool `json:"bo,omitzero"`
100+
101+
Ur uint `json:"ur"`
102+
Uo uint `json:"uo,omitzero"`
103+
104+
Str struct{} `json:"str"`
105+
Sto struct{} `json:"sto,omitzero"`
106+
107+
Time time.Time `json:"time,omitzero"`
108+
Nzs NonZeroStruct `json:"nzs,omitzero"`
109+
}
110+
111+
func TestOmitZero(t *testing.T) {
112+
var want = `{
113+
"sr": "",
114+
"omitzero": 0,
115+
"slr": null,
116+
"slononnil": [],
117+
"mr": {},
118+
"Mo": {},
119+
"fr": 0,
120+
"br": false,
121+
"ur": 0,
122+
"str": {},
123+
"nzs": {}
124+
}`
125+
var o OptionalsZero
126+
o.Sw = "something"
127+
o.SloNonNil = make([]string, 0)
128+
o.Mr = map[string]any{}
129+
o.Mo = map[string]any{}
130+
131+
got, err := MarshalIndent(&o, "", " ")
132+
if err != nil {
133+
t.Fatalf("MarshalIndent error: %v", err)
134+
}
135+
if got := string(got); got != want {
136+
t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want))
137+
}
138+
}
139+
140+
type OptionalsEmptyZero struct {
141+
Sr string `json:"sr"`
142+
So string `json:"so,omitempty,omitzero"`
143+
Sw string `json:"-"`
144+
145+
Io int `json:"io,omitempty,omitzero"`
146+
147+
Slr []string `json:"slr,random"`
148+
Slo []string `json:"slo,omitempty,omitzero"`
149+
SloNonNil []string `json:"slononnil,omitempty,omitzero"`
150+
151+
Mr map[string]any `json:"mr"`
152+
Mo map[string]any `json:",omitempty,omitzero"`
153+
154+
Fr float64 `json:"fr"`
155+
Fo float64 `json:"fo,omitempty,omitzero"`
156+
157+
Br bool `json:"br"`
158+
Bo bool `json:"bo,omitempty,omitzero"`
159+
160+
Ur uint `json:"ur"`
161+
Uo uint `json:"uo,omitempty,omitzero"`
162+
163+
Str struct{} `json:"str"`
164+
Sto struct{} `json:"sto,omitempty,omitzero"`
165+
166+
Time time.Time `json:"time,omitempty,omitzero"`
167+
Nzs NonZeroStruct `json:"nzs,omitzero"`
168+
}
169+
170+
func TestOmitEmptyZero(t *testing.T) {
171+
var want = `{
172+
"sr": "",
173+
"slr": null,
174+
"mr": {},
175+
"fr": 0,
176+
"br": false,
177+
"ur": 0,
178+
"str": {},
179+
"nzs": {}
180+
}`
181+
var o OptionalsEmptyZero
182+
o.Sw = "something"
183+
o.SloNonNil = make([]string, 0)
184+
o.Mr = map[string]any{}
185+
o.Mo = map[string]any{}
186+
187+
got, err := MarshalIndent(&o, "", " ")
188+
if err != nil {
189+
t.Fatalf("MarshalIndent error: %v", err)
190+
}
191+
if got := string(got); got != want {
192+
t.Errorf("MarshalIndent:\n\tgot: %s\n\twant: %s\n", indentNewlines(got), indentNewlines(want))
193+
}
194+
}
195+
73196
type StringTag struct {
74197
BoolStr bool `json:",string"`
75198
IntStr int64 `json:",string"`

0 commit comments

Comments
 (0)