From c58bf4cfef76fcd9a377452b5c3cb84d5f20ce98 Mon Sep 17 00:00:00 2001 From: Cody Oss <6331106+codyoss@users.noreply.github.com> Date: Wed, 14 Dec 2022 20:20:15 +0000 Subject: [PATCH] feat: support set null map entries for non-simple map values (#1782) Previous impl assumed maps would be of type map[string]string. Although this is most of the map types this library uses some use more complex types. For these cases it is impossible to unset fields in patch requests until this fix. Reported internal Ref: b/261221901 --- internal/gensupport/json.go | 26 +++++++++++++++++++++++++- internal/gensupport/json_test.go | 26 ++++++++++++++++++++------ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/internal/gensupport/json.go b/internal/gensupport/json.go index 1b770464110..eab49a11eb1 100644 --- a/internal/gensupport/json.go +++ b/internal/gensupport/json.go @@ -86,7 +86,12 @@ func schemaToMap(schema interface{}, mustInclude, useNull map[string]bool, useNu if f.Type.Kind() == reflect.Map && useNullMaps[f.Name] != nil { ms, ok := v.Interface().(map[string]string) if !ok { - return nil, fmt.Errorf("field %q has keys in NullFields but is not a map[string]string", f.Name) + mi, err := initMapSlow(v, f.Name, useNullMaps) + if err != nil { + return nil, err + } + m[tag.apiName] = mi + continue } mi := map[string]interface{}{} for k, v := range ms { @@ -120,6 +125,25 @@ func schemaToMap(schema interface{}, mustInclude, useNull map[string]bool, useNu return m, nil } +// initMapSlow uses reflection to build up a map object. This is slower than +// the default behavior so it should be used only as a fallback. +func initMapSlow(rv reflect.Value, fieldName string, useNullMaps map[string]map[string]bool) (map[string]interface{}, error) { + mi := map[string]interface{}{} + iter := rv.MapRange() + for iter.Next() { + k, ok := iter.Key().Interface().(string) + if !ok { + return nil, fmt.Errorf("field %q has keys in NullFields but is not a map[string]any", fieldName) + } + v := iter.Value().Interface() + mi[k] = v + } + for k := range useNullMaps[fieldName] { + mi[k] = nil + } + return mi, nil +} + // formatAsString returns a string representation of v, dereferencing it first if possible. func formatAsString(v reflect.Value, kind reflect.Kind) string { if kind == reflect.Ptr && !v.IsNil() { diff --git a/internal/gensupport/json_test.go b/internal/gensupport/json_test.go index 2cc5dfbbb90..d5ae3a9a14b 100644 --- a/internal/gensupport/json_test.go +++ b/internal/gensupport/json_test.go @@ -12,6 +12,10 @@ import ( "google.golang.org/api/googleapi" ) +type CustomType struct { + Foo string `json:"foo,omitempty"` +} + type schema struct { // Basic types B bool `json:"b,omitempty"` @@ -28,12 +32,13 @@ type schema struct { PStr *string `json:"pstr,omitempty"` // Other types - Int64s googleapi.Int64s `json:"i64s,omitempty"` - S []int `json:"s,omitempty"` - M map[string]string `json:"m,omitempty"` - Any interface{} `json:"any,omitempty"` - Child *child `json:"child,omitempty"` - MapToAnyArray map[string][]interface{} `json:"maptoanyarray,omitempty"` + Int64s googleapi.Int64s `json:"i64s,omitempty"` + S []int `json:"s,omitempty"` + M map[string]string `json:"m,omitempty"` + Any interface{} `json:"any,omitempty"` + Child *child `json:"child,omitempty"` + MapToAnyArray map[string][]interface{} `json:"maptoanyarray,omitempty"` + MapToCustomType map[string]CustomType `json:"maptocustomtype,omitempty"` ForceSendFields []string `json:"-"` NullFields []string `json:"-"` @@ -254,6 +259,15 @@ func TestMapField(t *testing.T) { }, want: `{}`, }, + { + s: schema{ + MapToCustomType: map[string]CustomType{ + "a": {Foo: "foo"}, + }, + NullFields: []string{"MapToCustomType.b"}, + }, + want: `{"maptocustomtype": {"a": {"foo": "foo"}, "b": null}}`, + }, } { checkMarshalJSON(t, tc) }