Skip to content

Commit aacdc51

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

File tree

3 files changed

+143
-3
lines changed

3 files changed

+143
-3
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
When marshaling, the `omitzero` option specifies that the struct field should be
2+
omitted if the field value is zero as determined by the `IsZero() bool` method
3+
if present, otherwise based on whether the field is the zero Go value (according
4+
to [reflect.Value.IsZero]).
5+
6+
This option has no effect when unmarshaling. If `omitempty` is specified together
7+
with `omitzero`, whether a field is omitted is based on the logical OR of the two.
8+
9+
This will mean that `omitzero` of a slice omits a nil slice but emits [] for a
10+
zero-length non-nil slice (and similar for maps). It will also mean that
11+
`omitzero` of a [time.Time] omits time.Time{}.

src/encoding/json/encode.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,17 @@ func isEmptyValue(v reflect.Value) bool {
318318
return false
319319
}
320320

321+
type zeroable interface {
322+
IsZero() bool
323+
}
324+
325+
func isZeroValue(v reflect.Value) bool {
326+
if z, ok := v.Interface().(zeroable); ok {
327+
return z.IsZero()
328+
}
329+
return v.IsZero()
330+
}
331+
321332
func (e *encodeState) reflectValue(v reflect.Value, opts encOpts) {
322333
valueEncoder(v)(e, v, opts)
323334
}
@@ -701,7 +712,8 @@ FieldLoop:
701712
fv = fv.Field(i)
702713
}
703714

704-
if f.omitEmpty && isEmptyValue(fv) {
715+
if (f.omitEmpty && isEmptyValue(fv)) ||
716+
(f.omitZero && isZeroValue(fv)) {
705717
continue
706718
}
707719
e.WriteByte(next)
@@ -1048,6 +1060,7 @@ type field struct {
10481060
index []int
10491061
typ reflect.Type
10501062
omitEmpty bool
1063+
omitZero bool
10511064
quoted bool
10521065

10531066
encoder encoderFunc
@@ -1154,6 +1167,7 @@ func typeFields(t reflect.Type) structFields {
11541167
index: index,
11551168
typ: ft,
11561169
omitEmpty: opts.Contains("omitempty"),
1170+
omitZero: opts.Contains("omitzero"),
11571171
quoted: quoted,
11581172
}
11591173
field.nameBytes = []byte(field.name)

src/encoding/json/encode_test.go

Lines changed: 117 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,120 @@ func TestOmitEmpty(t *testing.T) {
7071
}
7172
}
7273

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

0 commit comments

Comments
 (0)