From 2f3aa09a6cc30a2b94effe18288ccd3780e9d110 Mon Sep 17 00:00:00 2001 From: Cody Oss Date: Wed, 14 Dec 2022 13:50:33 -0600 Subject: [PATCH] feat: support set null map entries for non-simple map values 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) }