Skip to content

Commit 854f399

Browse files
Alifarid0011willnorris
authored andcommitted
query: honor omitempty on embedded struct fields
1 parent 965d79f commit 854f399

2 files changed

Lines changed: 58 additions & 4 deletions

File tree

‎query/encode.go‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,10 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error {
165165
}
166166
name, opts := parseTag(tag)
167167

168+
if opts.Contains("omitempty") && isEmptyValue(sv) {
169+
continue
170+
}
171+
168172
if name == "" {
169173
if sf.Anonymous {
170174
v := reflect.Indirect(sv)
@@ -182,10 +186,6 @@ func reflectValue(values url.Values, val reflect.Value, scope string) error {
182186
name = scope + "[" + name + "]"
183187
}
184188

185-
if opts.Contains("omitempty") && isEmptyValue(sv) {
186-
continue
187-
}
188-
189189
// unwrap interface values so the concrete type's Encoder is used
190190
if sv.Kind() == reflect.Interface && !sv.IsNil() {
191191
sv = sv.Elem()
@@ -334,6 +334,10 @@ func isEmptyValue(v reflect.Value) bool {
334334
return v.IsNil()
335335
}
336336

337+
if !v.CanInterface() {
338+
return false
339+
}
340+
337341
type zeroable interface {
338342
IsZero() bool
339343
}

‎query/encode_test.go‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,56 @@ func TestValues_EmbeddedStructs(t *testing.T) {
424424
}
425425
}
426426

427+
// OptionalEmbedded supports omitting all of its promoted fields.
428+
type OptionalEmbedded struct {
429+
Value string `url:"value"`
430+
}
431+
432+
func (v OptionalEmbedded) IsZero() bool { return v.Value == "" }
433+
434+
func TestValues_EmbeddedOmitEmpty(t *testing.T) {
435+
type Options struct {
436+
OptionalEmbedded `url:",omitempty"`
437+
}
438+
type privateScalar string
439+
type privateEmbedded struct {
440+
Value string `url:"value"`
441+
}
442+
tests := []struct {
443+
name string
444+
input interface{}
445+
want url.Values
446+
}{
447+
{"empty", Options{}, url.Values{}},
448+
{"nonempty", Options{OptionalEmbedded{Value: "present"}}, url.Values{"value": {"present"}}},
449+
{"without option", struct{ OptionalEmbedded }{}, url.Values{"value": {""}}},
450+
{"named empty", struct {
451+
OptionalEmbedded `url:"nested,omitempty"`
452+
}{}, url.Values{}},
453+
{"named nonempty", struct {
454+
OptionalEmbedded `url:"nested,omitempty"`
455+
}{OptionalEmbedded{Value: "present"}}, url.Values{"nested[value]": {"present"}}},
456+
{"nil pointer", struct {
457+
*OptionalEmbedded `url:",omitempty"`
458+
}{}, url.Values{}},
459+
{"non-nil pointer", struct {
460+
*OptionalEmbedded `url:",omitempty"`
461+
}{&OptionalEmbedded{}}, url.Values{"value": {""}}},
462+
{"empty unexported scalar", struct {
463+
privateScalar `url:",omitempty"`
464+
}{}, url.Values{}},
465+
{"nil unexported pointer", struct {
466+
*privateScalar `url:",omitempty"`
467+
}{}, url.Values{}},
468+
{"unexported embed", struct {
469+
privateEmbedded `url:",omitempty"`
470+
}{privateEmbedded{Value: "present"}}, url.Values{"value": {"present"}}},
471+
}
472+
for _, tt := range tests {
473+
t.Run(tt.name, func(t *testing.T) { testValue(t, tt.input, tt.want) })
474+
}
475+
}
476+
427477
func TestValues_InvalidInput(t *testing.T) {
428478
_, err := Values("")
429479
if err == nil {

0 commit comments

Comments
 (0)