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) }