Skip to content
This repository has been archived by the owner on Jul 22, 2024. It is now read-only.

Dereference multiple pointers when decoding to maps #348

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 65 additions & 60 deletions mapstructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,84 +9,84 @@
//
// The simplest function to start with is Decode.
//
// Field Tags
// # Field Tags
//
// When decoding to a struct, mapstructure will use the field name by
// default to perform the mapping. For example, if a struct has a field
// "Username" then mapstructure will look for a key in the source value
// of "username" (case insensitive).
//
// type User struct {
// Username string
// }
// type User struct {
// Username string
// }
//
// You can change the behavior of mapstructure by using struct tags.
// The default struct tag that mapstructure looks for is "mapstructure"
// but you can customize it using DecoderConfig.
//
// Renaming Fields
// # Renaming Fields
//
// To rename the key that mapstructure looks for, use the "mapstructure"
// tag and set a value directly. For example, to change the "username" example
// above to "user":
//
// type User struct {
// Username string `mapstructure:"user"`
// }
// type User struct {
// Username string `mapstructure:"user"`
// }
//
// Embedded Structs and Squashing
// # Embedded Structs and Squashing
//
// Embedded structs are treated as if they're another field with that name.
// By default, the two structs below are equivalent when decoding with
// mapstructure:
//
// type Person struct {
// Name string
// }
// type Person struct {
// Name string
// }
//
// type Friend struct {
// Person
// }
// type Friend struct {
// Person
// }
//
// type Friend struct {
// Person Person
// }
// type Friend struct {
// Person Person
// }
//
// This would require an input that looks like below:
//
// map[string]interface{}{
// "person": map[string]interface{}{"name": "alice"},
// }
// map[string]interface{}{
// "person": map[string]interface{}{"name": "alice"},
// }
//
// If your "person" value is NOT nested, then you can append ",squash" to
// your tag value and mapstructure will treat it as if the embedded struct
// were part of the struct directly. Example:
//
// type Friend struct {
// Person `mapstructure:",squash"`
// }
// type Friend struct {
// Person `mapstructure:",squash"`
// }
//
// Now the following input would be accepted:
//
// map[string]interface{}{
// "name": "alice",
// }
// map[string]interface{}{
// "name": "alice",
// }
//
// When decoding from a struct to a map, the squash tag squashes the struct
// fields into a single map. Using the example structs from above:
//
// Friend{Person: Person{Name: "alice"}}
// Friend{Person: Person{Name: "alice"}}
//
// Will be decoded into a map:
//
// map[string]interface{}{
// "name": "alice",
// }
// map[string]interface{}{
// "name": "alice",
// }
//
// DecoderConfig has a field that changes the behavior of mapstructure
// to always squash embedded structs.
//
// Remainder Values
// # Remainder Values
//
// If there are any unmapped keys in the source value, mapstructure by
// default will silently ignore them. You can error by setting ErrorUnused
Expand All @@ -98,20 +98,20 @@
// probably be a "map[string]interface{}" or "map[interface{}]interface{}".
// See example below:
//
// type Friend struct {
// Name string
// Other map[string]interface{} `mapstructure:",remain"`
// }
// type Friend struct {
// Name string
// Other map[string]interface{} `mapstructure:",remain"`
// }
//
// Given the input below, Other would be populated with the other
// values that weren't used (everything but "name"):
//
// map[string]interface{}{
// "name": "bob",
// "address": "123 Maple St.",
// }
// map[string]interface{}{
// "name": "bob",
// "address": "123 Maple St.",
// }
//
// Omit Empty Values
// # Omit Empty Values
//
// When decoding from a struct to any other value, you may use the
// ",omitempty" suffix on your tag to omit that value if it equates to
Expand All @@ -122,37 +122,37 @@
// field value is zero and a numeric type, the field is empty, and it won't
// be encoded into the destination type.
//
// type Source struct {
// Age int `mapstructure:",omitempty"`
// }
// type Source struct {
// Age int `mapstructure:",omitempty"`
// }
//
// Unexported fields
// # Unexported fields
//
// Since unexported (private) struct fields cannot be set outside the package
// where they are defined, the decoder will simply skip them.
//
// For this output type definition:
//
// type Exported struct {
// private string // this unexported field will be skipped
// Public string
// }
// type Exported struct {
// private string // this unexported field will be skipped
// Public string
// }
//
// Using this map as input:
//
// map[string]interface{}{
// "private": "I will be ignored",
// "Public": "I made it through!",
// }
// map[string]interface{}{
// "private": "I will be ignored",
// "Public": "I made it through!",
// }
//
// The following struct will be decoded:
//
// type Exported struct {
// private: "" // field is left with an empty string (zero value)
// Public: "I made it through!"
// }
// type Exported struct {
// private: "" // field is left with an empty string (zero value)
// Public: "I made it through!"
// }
//
// Other Configuration
// # Other Configuration
//
// mapstructure is highly configurable. See the DecoderConfig struct
// for other features and options that are supported.
Expand Down Expand Up @@ -811,8 +811,14 @@ func (d *Decoder) decodeMap(name string, data interface{}, val reflect.Value) er
valMap = reflect.MakeMap(mapType)
}

dataVal := reflect.ValueOf(data)

// Resolve any levels of indirection
for dataVal.Kind() == reflect.Pointer {
dataVal = reflect.Indirect(dataVal)
}

// Check input type and based on the input type jump to the proper func
dataVal := reflect.Indirect(reflect.ValueOf(data))
switch dataVal.Kind() {
case reflect.Map:
return d.decodeMapFromMap(name, dataVal, val, valMap)
Expand Down Expand Up @@ -1190,7 +1196,6 @@ func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value)
if dataVal.Len() > arrayType.Len() {
return fmt.Errorf(
"'%s': expected source data to have length less or equal to %d, got %d", name, arrayType.Len(), dataVal.Len())

}

// Make a new array to hold our result, same size as the original data.
Expand Down
110 changes: 107 additions & 3 deletions mapstructure_bugs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,6 @@ func TestNestedTypePointerWithDefaults(t *testing.T) {
if result.Vbar.Vuint != 42 {
t.Errorf("vuint value should be 42: %#v", result.Vbar.Vuint)
}

}

type NestedSlice struct {
Expand Down Expand Up @@ -284,7 +283,6 @@ func TestNestedTypeWithDefaults(t *testing.T) {
if result.Vbar.Vuint != 42 {
t.Errorf("vuint value should be 42: %#v", result.Vbar.Vuint)
}

}

// #67 panic() on extending slices (decodeSlice with disabled ZeroValues)
Expand Down Expand Up @@ -526,7 +524,6 @@ func TestDecodeIntermediateMapsSettable(t *testing.T) {
return data, nil
},
})

if err != nil {
t.Fatalf("failed to create decoder: %v", err)
}
Expand Down Expand Up @@ -625,3 +622,110 @@ func TestMapOmitEmptyWithEmptyFieldnameInTag(t *testing.T) {
t.Fatalf("fail: %#v", m)
}
}

// GH-347: Decoding maps with multiple indirection results in an error
func TestDecodeToMapWithMultipleIndirection(t *testing.T) {
t.Run("Struct", func(t *testing.T) {
type Struct struct {
Foo string `mapstructure:"foo"`
}

v := Struct{
Foo: "bar",
}

i := &v
ii := &i
iii := &ii

var actual map[string]interface{}

if err := Decode(iii, &actual); err != nil {
t.Fatal(err)
}

expected := map[string]interface{}{
"foo": "bar",
}

if !reflect.DeepEqual(actual, expected) {
t.Fatalf("expected: %#v, got: %#v", expected, actual)
}
})

t.Run("Map", func(t *testing.T) {
v := map[string]interface{}{
"foo": "bar",
}

i := &v
ii := &i
iii := &ii

var actual map[string]interface{}

if err := Decode(iii, &actual); err != nil {
t.Fatal(err)
}

expected := map[string]interface{}{
"foo": "bar",
}

if !reflect.DeepEqual(actual, expected) {
t.Fatalf("expected: %#v, got: %#v", expected, actual)
}
})

t.Run("Array", func(t *testing.T) {
v := [1]map[string]interface{}{
{
"foo": "bar",
},
}

i := &v
ii := &i
iii := &ii

var actual map[string]interface{}

if err := WeakDecode(iii, &actual); err != nil {
t.Fatal(err)
}

expected := map[string]interface{}{
"foo": "bar",
}

if !reflect.DeepEqual(actual, expected) {
t.Fatalf("expected: %#v, got: %#v", expected, actual)
}
})

t.Run("Slice", func(t *testing.T) {
v := []map[string]interface{}{
{
"foo": "bar",
},
}

i := &v
ii := &i
iii := &ii

var actual map[string]interface{}

if err := WeakDecode(iii, &actual); err != nil {
t.Fatal(err)
}

expected := map[string]interface{}{
"foo": "bar",
}

if !reflect.DeepEqual(actual, expected) {
t.Fatalf("expected: %#v, got: %#v", expected, actual)
}
})
}