diff --git a/core/mapping/unmarshaler.go b/core/mapping/unmarshaler.go index fc843891b632..df7704b88b85 100644 --- a/core/mapping/unmarshaler.go +++ b/core/mapping/unmarshaler.go @@ -908,12 +908,16 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, valu } switch fieldKind { - case reflect.Array, reflect.Map, reflect.Slice: + case reflect.Array, reflect.Slice: if !opts.optional() { return u.processFieldNotFromString(fieldType, value, valueWithParent{ value: emptyMap, }, opts, fullName) } + case reflect.Map: + if !opts.optional() { + return newInitError(fullName) + } case reflect.Struct: if !opts.optional() { required, err := structValueRequired(u.key, derefedType) diff --git a/core/mapping/unmarshaler_test.go b/core/mapping/unmarshaler_test.go index 229d9c4134d7..307ff5de7ed0 100644 --- a/core/mapping/unmarshaler_test.go +++ b/core/mapping/unmarshaler_test.go @@ -2325,6 +2325,42 @@ func TestUnmarshalMapOfStruct(t *testing.T) { } assert.Error(t, UnmarshalKey(m, &v)) }) + + t.Run("map set", func(t *testing.T) { + type Inner1 struct { + M map[string]string + } + assert.Error(t, UnmarshalKey(map[string]any{}, &Inner1{})) + assert.NoError(t, UnmarshalKey(map[string]any{ + "M": map[string]string{}, + }, &Inner1{})) + + type Inner2 struct { + Inner1 + } + assert.Error(t, UnmarshalKey(map[string]any{}, &Inner2{})) + assert.NoError(t, UnmarshalKey(map[string]any{ + "M": map[string]string{}, + }, &Inner2{})) + + type Inner3 struct { + C Inner1 + } + assert.Error(t, UnmarshalKey(map[string]any{}, &Inner3{})) + assert.NoError(t, UnmarshalKey(map[string]any{ + "C": map[string]any{ + "M": map[string]string{}, + }, + }, &Inner3{})) + + type Inner4 struct { + M map[string]string `json:",optional"` + } + assert.NoError(t, UnmarshalKey(map[string]any{}, &Inner4{})) + assert.NoError(t, UnmarshalKey(map[string]any{ + "M": map[string]string{}, + }, &Inner4{})) + }) } func TestUnmarshalSlice(t *testing.T) {