From 93663c4d5b1f33be88f4083d3c7451ed0c1439fb Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 7 Jun 2020 15:57:03 -0700 Subject: [PATCH] If interface value is not addressable, make copy and set This fixes #187. If the value of an interface is not addressable, we can't decode into it because we can't replace any keys. To work around this, we copy the value, decode into that, and then replace the full value. --- mapstructure.go | 29 ++++++++++++++++++++++++++++- mapstructure_test.go | 22 ++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/mapstructure.go b/mapstructure.go index 84beaabd..b384d9d9 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -463,7 +463,34 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e // value to "data" of that type. func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error { if val.IsValid() && val.Elem().IsValid() { - return d.decode(name, data, val.Elem()) + elem := val.Elem() + + // If we can't address this element, then its not writable. Instead, + // we make a copy of the value (which is a pointer and therefore + // writable), decode into that, and replace the whole value. + copied := false + if !elem.CanAddr() { + copied = true + + // Make *T + copy := reflect.New(elem.Type()) + + // *T = elem + copy.Elem().Set(elem) + + // Set elem so we decode into it + elem = copy + } + + // Decode. If we have an error then return. We also return right + // away if we're not a copy because that means we decoded directly. + if err := d.decode(name, data, elem); err != nil || !copied { + return err + } + + // If we're a copy, we need to set te final result + val.Set(elem.Elem()) + return nil } dataVal := reflect.ValueOf(data) diff --git a/mapstructure_test.go b/mapstructure_test.go index 1701394a..63614e83 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go @@ -375,6 +375,28 @@ func TestBasic_interfaceStruct(t *testing.T) { } } +// Issue 187 +func TestBasic_interfaceStructNonPtr(t *testing.T) { + t.Parallel() + + input := map[string]interface{}{ + "vstring": "foo", + } + + var iface interface{} = Basic{} + err := Decode(input, &iface) + if err != nil { + t.Fatalf("got an err: %s", err) + } + + expected := Basic{ + Vstring: "foo", + } + if !reflect.DeepEqual(iface, expected) { + t.Fatalf("bad: %#v", iface) + } +} + func TestDecode_BasicSquash(t *testing.T) { t.Parallel()