Repository: mitchellh/mapstructure
Pull request #137 (2018-09-30 17:25:57)

Struct to struct decoding + bug fixes
This introduces struct to struct decoding for decoding similar structs to other similar but differently typed structs. Along the way, this fixes some issues found that are more common with structs but could've affected non-struct decoding as well.

This is particularly useful for close-but-not-quite-the-same structs. Example:

```go
type A struct {
  Input string
}

type B struct {
  Input *string
}

// Can now convert like this:
mapstructure.Decode(&a, &b)
```

Additional bugs fixed:

  * Decoding a pointer like `*[]string` to a non-pointer `[]string` didn't work at all, this is fixed.
  * Decoding a non-pointer `[]string` to a pointer `*[]string` should keep the pointer `nil` if the source slice is nil.
  * Decoding a map to a map should set the destination map to nil if the source is nil (vs. empty).

**No tests were changed, only added,** so existing behavior should be preserved. 


Patch:
diff --git a/mapstructure.go b/mapstructure.go
index d3222b8..32eb9a6 100644
--- a/mapstructure.go
+++ b/mapstructure.go
@@ -224,6 +224,17 @@ func (d *Decoder) Decode(input interface{}) error {
 
 // Decodes an unknown data type into a specific reflection value.
 func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) error {
+	var inputVal reflect.Value
+	if input != nil {
+		inputVal = reflect.ValueOf(input)
+
+		// We need to check here if input is a typed nil. Typed nils won't
+		// match the "input == nil" below so we check that here.
+		if inputVal.Kind() == reflect.Ptr && inputVal.IsNil() {
+			input = nil
+		}
+	}
+
 	if input == nil {
 		// If the data is nil, then we don't set anything, unless ZeroFields is set
 		// to true.
@@ -237,7 +248,6 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
 		return nil
 	}
 
-	inputVal := reflect.ValueOf(input)
 	if !inputVal.IsValid() {
 		// If the input value is invalid, then we just set the value
 		// to be the zero value.
@@ -260,8 +270,8 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
 	}
 
 	var err error
-	inputKind := getKind(outVal)
-	switch inputKind {
+	outputKind := getKind(outVal)
+	switch outputKind {
 	case reflect.Bool:
 		err = d.decodeBool(name, input, outVal)
 	case reflect.Interface:
@@ -288,7 +298,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
 		err = d.decodeFunc(name, input, outVal)
 	default:
 		// If we reached this point then we weren't able to decode it
-		return fmt.Errorf("%s: unsupported type: %s", name, inputKind)
+		return fmt.Errorf("%s: unsupported type: %s", name, outputKind)
 	}
 
 	// If we reached here, then we successfully decoded SOMETHING, so
@@ -306,7 +316,7 @@ func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value)
 	if val.IsValid() && val.Elem().IsValid() {
 		return d.decode(name, data, val.Elem())
 	}
-	dataVal := reflect.ValueOf(data)
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
 	if !dataVal.IsValid() {
 		dataVal = reflect.Zero(val.Type())
 	}
@@ -323,7 +333,7 @@ func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value)
 }
 
 func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value) error {
-	dataVal := reflect.ValueOf(data)
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
 	dataKind := getKind(dataVal)
 
 	converted := true
@@ -375,7 +385,7 @@ func (d *Decoder) decodeString(name string, data interface{}, val reflect.Value)
 }
 
 func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) error {
-	dataVal := reflect.ValueOf(data)
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
 	dataKind := getKind(dataVal)
 	dataType := dataVal.Type()
 
@@ -417,7 +427,7 @@ func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) er
 }
 
 func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error {
-	dataVal := reflect.ValueOf(data)
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
 	dataKind := getKind(dataVal)
 
 	switch {
@@ -460,7 +470,7 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e
 }
 
 func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) error {
-	dataVal := reflect.ValueOf(data)
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
 	dataKind := getKind(dataVal)
 
 	switch {
@@ -491,7 +501,7 @@ func (d *Decoder) decodeBool(name string, data interface{}, val reflect.Value) e
 }
 
 func (d *Decoder) decodeFloat(name string, data interface{}, val reflect.Value) error {
-	dataVal := reflect.ValueOf(data)
+	dataVal := reflect.Indirect(reflect.ValueOf(data))
 	dataKind := getKind(dataVal)
 	dataType := dataVal.Type()
 
@@ -595,6 +605,20 @@ func (d *Decoder) decodeMapFromMap(name string, dataVal reflect.Value, val refle
 	// Accumulate errors
 	errors := make([]string, 0)
 
+	// If the input data is empty, then we just match what the input data is.
+	if dataVal.Len() == 0 {
+		if dataVal.IsNil() {
+			if !val.IsNil() {
+				val.Set(dataVal)
+			}
+		} else {
+			// Set to empty allocated value
+			val.Set(valMap)
+		}
+
+		return nil
+	}
+
 	for _, k := range dataVal.MapKeys() {
 		fieldName := fmt.Sprintf("%s[%s]", name, k)
 
@@ -706,11 +730,21 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
 }
 
 func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error {
+	// If the input data is nil, then we want to just set the output
+	// pointer to be nil as well.
+	if data == nil || reflect.Indirect(reflect.ValueOf(data)).IsNil() {
+		if !val.IsNil() && val.CanSet() {
+			nilValue := reflect.New(val.Type()).Elem()
+			val.Set(nilValue)
+		}
+
+		return nil
+	}
+
 	// Create an element of the concrete (non pointer) type and decode
 	// into that. Then set the value of the pointer to this type.
 	valType := val.Type()
 	valElemType := valType.Elem()
-
 	if val.CanSet() {
 		realVal := val
 		if realVal.IsNil() || d.config.ZeroFields {
@@ -752,33 +786,44 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value)
 
 	valSlice := val
 	if valSlice.IsNil() || d.config.ZeroFields {
-		// Check input type
-		if dataValKind != reflect.Array && dataValKind != reflect.Slice {
-			if d.config.WeaklyTypedInput {
-				switch {
-				// Empty maps turn into empty slices
-				case dataValKind == reflect.Map:
-					if dataVal.Len() == 0 {
-						val.Set(reflect.MakeSlice(sliceType, 0, 0))
-						return nil
-					}
-					// Create slice of maps of other sizes
-					return d.decodeSlice(name, []interface{}{data}, val)
+		if d.config.WeaklyTypedInput {
+			switch {
+			// Slice and array we use the normal logic
+			case dataValKind == reflect.Slice, dataValKind == reflect.Array:
+				break
 
-				case dataValKind == reflect.String && valElemType.Kind() == reflect.Uint8:
-					return d.decodeSlice(name, []byte(dataVal.String()), val)
-				// All other types we try to convert to the slice type
-				// and "lift" it into it. i.e. a string becomes a string slice.
-				default:
-					// Just re-try this function with data as a slice.
-					return d.decodeSlice(name, []interface{}{data}, val)
+			// Empty maps turn into empty slices
+			case dataValKind == reflect.Map:
+				if dataVal.Len() == 0 {
+					val.Set(reflect.MakeSlice(sliceType, 0, 0))
+					return nil
 				}
+				// Create slice of maps of other sizes
+				return d.decodeSlice(name, []interface{}{data}, val)
+
+			case dataValKind == reflect.String && valElemType.Kind() == reflect.Uint8:
+				return d.decodeSlice(name, []byte(dataVal.String()), val)
+
+			// All other types we try to convert to the slice type
+			// and "lift" it into it. i.e. a string becomes a string slice.
+			default:
+				// Just re-try this function with data as a slice.
+				return d.decodeSlice(name, []interface{}{data}, val)
 			}
+		}
+
+		// Check input type
+		if dataValKind != reflect.Array && dataValKind != reflect.Slice {
 			return fmt.Errorf(
 				"'%s': source data must be an array or slice, got %s", name, dataValKind)
 
 		}
 
+		// If the input value is empty, then don't allocate since non-nil != nil
+		if dataVal.Len() == 0 {
+			return nil
+		}
+
 		// Make a new slice to hold our result, same size as the original data.
 		valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len())
 	}
@@ -888,10 +933,29 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value)
 	}
 
 	dataValKind := dataVal.Kind()
-	if dataValKind != reflect.Map {
-		return fmt.Errorf("'%s' expected a map, got '%s'", name, dataValKind)
+	switch dataValKind {
+	case reflect.Map:
+		return d.decodeStructFromMap(name, dataVal, val)
+
+	case reflect.Struct:
+		// Not the most efficient way to do this but we can optimize later if
+		// we want to. To convert from struct to struct we go to map first
+		// as an intermediary.
+		m := make(map[string]interface{})
+		mval := reflect.Indirect(reflect.ValueOf(&m))
+		if err := d.decodeMapFromStruct(name, dataVal, mval, mval); err != nil {
+			return err
+		}
+
+		result := d.decodeStructFromMap(name, mval, val)
+		return result
+
+	default:
+		return fmt.Errorf("'%s' expected a map, got '%s'", name, dataVal.Kind())
 	}
+}
 
+func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) error {
 	dataValType := dataVal.Type()
 	if kind := dataValType.Key().Kind(); kind != reflect.String && kind != reflect.Interface {
 		return fmt.Errorf(


Test patch:
diff --git a/mapstructure_test.go b/mapstructure_test.go
index 344ffa3..141d054 100644
--- a/mapstructure_test.go
+++ b/mapstructure_test.go
@@ -23,6 +23,20 @@ type Basic struct {
 	VjsonNumber json.Number
 }
 
+type BasicPointer struct {
+	Vstring     *string
+	Vint        *int
+	Vuint       *uint
+	Vbool       *bool
+	Vfloat      *float64
+	Vextra      *string
+	vsilent     *bool
+	Vdata       *interface{}
+	VjsonInt    *int
+	VjsonFloat  *float64
+	VjsonNumber *json.Number
+}
+
 type BasicSquash struct {
 	Test Basic `mapstructure:",squash"`
 }
@@ -88,10 +102,19 @@ type Slice struct {
 	Vbar []string
 }
 
+type SliceOfAlias struct {
+	Vfoo string
+	Vbar SliceAlias
+}
+
 type SliceOfStruct struct {
 	Value []Basic
 }
 
+type SlicePointer struct {
+	Vbar *[]string
+}
+
 type Array struct {
 	Vfoo string
 	Vbar [2]string
@@ -1290,9 +1313,15 @@ func TestArrayToMap(t *testing.T) {
 	}
 }
 
-func TestMapOutputForStructuredInputs(t *testing.T) {
+func TestDecodeTable(t *testing.T) {
 	t.Parallel()
 
+	// We need to make new types so that we don't get the short-circuit
+	// copy functionality. We want to test the deep copying functionality.
+	type BasicCopy Basic
+	type NestedPointerCopy NestedPointer
+	type MapCopy Map
+
 	tests := []struct {
 		name    string
 		in      interface{}
@@ -1360,6 +1389,101 @@ func TestMapOutputForStructuredInputs(t *testing.T) {
 			},
 			false,
 		},
+		{
+			"struct => struct",
+			&Basic{
+				Vstring: "vstring",
+				Vint:    2,
+				Vuint:   3,
+				Vbool:   true,
+				Vfloat:  4.56,
+				Vextra:  "vextra",
+				Vdata:   []byte("data"),
+				vsilent: true,
+			},
+			&BasicCopy{},
+			&BasicCopy{
+				Vstring: "vstring",
+				Vint:    2,
+				Vuint:   3,
+				Vbool:   true,
+				Vfloat:  4.56,
+				Vextra:  "vextra",
+				Vdata:   []byte("data"),
+			},
+			false,
+		},
+		{
+			"struct => struct with pointers",
+			&NestedPointer{
+				Vfoo: "hello",
+				Vbar: nil,
+			},
+			&NestedPointerCopy{},
+			&NestedPointerCopy{
+				Vfoo: "hello",
+			},
+			false,
+		},
+		{
+			"basic pointer to non-pointer",
+			&BasicPointer{
+				Vstring: stringPtr("vstring"),
+				Vint:    intPtr(2),
+				Vuint:   uintPtr(3),
+				Vbool:   boolPtr(true),
+				Vfloat:  floatPtr(4.56),
+				Vdata:   interfacePtr([]byte("data")),
+			},
+			&Basic{},
+			&Basic{
+				Vstring: "vstring",
+				Vint:    2,
+				Vuint:   3,
+				Vbool:   true,
+				Vfloat:  4.56,
+				Vdata:   []byte("data"),
+			},
+			false,
+		},
+		{
+			"slice non-pointer to pointer",
+			&Slice{},
+			&SlicePointer{},
+			&SlicePointer{},
+			false,
+		},
+		{
+			"slice non-pointer to pointer, zero field",
+			&Slice{},
+			&SlicePointer{
+				Vbar: &[]string{"yo"},
+			},
+			&SlicePointer{},
+			false,
+		},
+		{
+			"slice to slice alias",
+			&Slice{},
+			&SliceOfAlias{},
+			&SliceOfAlias{},
+			false,
+		},
+		{
+			"nil map to map",
+			&Map{},
+			&MapCopy{},
+			&MapCopy{},
+			false,
+		},
+		{
+			"nil map to non-empty map",
+			&Map{},
+			&MapCopy{Vother: map[string]string{"foo": "bar"}},
+			&MapCopy{},
+			false,
+		},
+
 		{
 			"slice input - should error",
 			[]string{"foo", "bar"},
@@ -1805,3 +1929,10 @@ func testArrayInput(t *testing.T, input map[string]interface{}, expected *Array)
 		}
 	}
 }
+
+func stringPtr(v string) *string              { return &v }
+func intPtr(v int) *int                       { return &v }
+func uintPtr(v uint) *uint                    { return &v }
+func boolPtr(v bool) *bool                    { return &v }
+func floatPtr(v float64) *float64             { return &v }
+func interfacePtr(v interface{}) *interface{} { return &v }