Skip to content

Commit c58bf4c

Browse files
authored
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
1 parent e4271df commit c58bf4c

2 files changed

Lines changed: 45 additions & 7 deletions

File tree

internal/gensupport/json.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,12 @@ func schemaToMap(schema interface{}, mustInclude, useNull map[string]bool, useNu
8686
if f.Type.Kind() == reflect.Map && useNullMaps[f.Name] != nil {
8787
ms, ok := v.Interface().(map[string]string)
8888
if !ok {
89-
return nil, fmt.Errorf("field %q has keys in NullFields but is not a map[string]string", f.Name)
89+
mi, err := initMapSlow(v, f.Name, useNullMaps)
90+
if err != nil {
91+
return nil, err
92+
}
93+
m[tag.apiName] = mi
94+
continue
9095
}
9196
mi := map[string]interface{}{}
9297
for k, v := range ms {
@@ -120,6 +125,25 @@ func schemaToMap(schema interface{}, mustInclude, useNull map[string]bool, useNu
120125
return m, nil
121126
}
122127

128+
// initMapSlow uses reflection to build up a map object. This is slower than
129+
// the default behavior so it should be used only as a fallback.
130+
func initMapSlow(rv reflect.Value, fieldName string, useNullMaps map[string]map[string]bool) (map[string]interface{}, error) {
131+
mi := map[string]interface{}{}
132+
iter := rv.MapRange()
133+
for iter.Next() {
134+
k, ok := iter.Key().Interface().(string)
135+
if !ok {
136+
return nil, fmt.Errorf("field %q has keys in NullFields but is not a map[string]any", fieldName)
137+
}
138+
v := iter.Value().Interface()
139+
mi[k] = v
140+
}
141+
for k := range useNullMaps[fieldName] {
142+
mi[k] = nil
143+
}
144+
return mi, nil
145+
}
146+
123147
// formatAsString returns a string representation of v, dereferencing it first if possible.
124148
func formatAsString(v reflect.Value, kind reflect.Kind) string {
125149
if kind == reflect.Ptr && !v.IsNil() {

internal/gensupport/json_test.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import (
1212
"google.golang.org/api/googleapi"
1313
)
1414

15+
type CustomType struct {
16+
Foo string `json:"foo,omitempty"`
17+
}
18+
1519
type schema struct {
1620
// Basic types
1721
B bool `json:"b,omitempty"`
@@ -28,12 +32,13 @@ type schema struct {
2832
PStr *string `json:"pstr,omitempty"`
2933

3034
// Other types
31-
Int64s googleapi.Int64s `json:"i64s,omitempty"`
32-
S []int `json:"s,omitempty"`
33-
M map[string]string `json:"m,omitempty"`
34-
Any interface{} `json:"any,omitempty"`
35-
Child *child `json:"child,omitempty"`
36-
MapToAnyArray map[string][]interface{} `json:"maptoanyarray,omitempty"`
35+
Int64s googleapi.Int64s `json:"i64s,omitempty"`
36+
S []int `json:"s,omitempty"`
37+
M map[string]string `json:"m,omitempty"`
38+
Any interface{} `json:"any,omitempty"`
39+
Child *child `json:"child,omitempty"`
40+
MapToAnyArray map[string][]interface{} `json:"maptoanyarray,omitempty"`
41+
MapToCustomType map[string]CustomType `json:"maptocustomtype,omitempty"`
3742

3843
ForceSendFields []string `json:"-"`
3944
NullFields []string `json:"-"`
@@ -254,6 +259,15 @@ func TestMapField(t *testing.T) {
254259
},
255260
want: `{}`,
256261
},
262+
{
263+
s: schema{
264+
MapToCustomType: map[string]CustomType{
265+
"a": {Foo: "foo"},
266+
},
267+
NullFields: []string{"MapToCustomType.b"},
268+
},
269+
want: `{"maptocustomtype": {"a": {"foo": "foo"}, "b": null}}`,
270+
},
257271
} {
258272
checkMarshalJSON(t, tc)
259273
}

0 commit comments

Comments
 (0)