diff --git a/mapstructure.go b/mapstructure.go index 7581806a..89d5436a 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -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 @@ -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 @@ -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. @@ -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) @@ -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. diff --git a/mapstructure_bugs_test.go b/mapstructure_bugs_test.go index 77bb3132..da5c8997 100644 --- a/mapstructure_bugs_test.go +++ b/mapstructure_bugs_test.go @@ -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 { @@ -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) @@ -526,7 +524,6 @@ func TestDecodeIntermediateMapsSettable(t *testing.T) { return data, nil }, }) - if err != nil { t.Fatalf("failed to create decoder: %v", err) } @@ -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) + } + }) +}