Skip to content

Commit

Permalink
feat: support set null map entries for non-simple map values (#1782)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
codyoss committed Dec 14, 2022
1 parent e4271df commit c58bf4c
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 7 deletions.
26 changes: 25 additions & 1 deletion internal/gensupport/json.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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() {
Expand Down
26 changes: 20 additions & 6 deletions internal/gensupport/json_test.go
Expand Up @@ -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"`
Expand All @@ -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:"-"`
Expand Down Expand Up @@ -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)
}
Expand Down

0 comments on commit c58bf4c

Please sign in to comment.