diff --git a/mapstructure.go b/mapstructure.go index 557f22c..27f21bc 100644 --- a/mapstructure.go +++ b/mapstructure.go @@ -962,6 +962,18 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re if v.Kind() != reflect.Struct { return fmt.Errorf("cannot squash non-struct type '%s'", v.Type()) } + } else { + if strings.Index(tagValue[index+1:], "remain") != -1 { + if v.Kind() != reflect.Map { + return fmt.Errorf("error remain-tag field with invalid type: '%s'", v.Type()) + } + + ptr := v.MapRange() + for ptr.Next() { + valMap.SetMapIndex(ptr.Key(), ptr.Value()) + } + continue + } } if keyNameTagValue := tagValue[:index]; keyNameTagValue != "" { keyName = keyNameTagValue diff --git a/mapstructure_benchmark_test.go b/mapstructure_benchmark_test.go index 534caa2..db50fea 100644 --- a/mapstructure_benchmark_test.go +++ b/mapstructure_benchmark_test.go @@ -283,3 +283,29 @@ func Benchmark_DecodeTagged(b *testing.B) { Decode(input, &result) } } + +func Benchmark_DecodeWithRemainingFields(b *testing.B) { + type Person struct { + Name string + Other map[string]interface{} `mapstructure:",remain"` + } + + input := map[string]interface{}{ + "name": "Luffy", + "age": 19, + "powers": []string{ + "Rubber Man", + "Conqueror Haki", + }, + } + + for i := 0; i < b.N; i++ { + // Decoding Map -> Struct + var person Person + _ = Decode(input, &person) + + // Decoding Struct -> Map + result := make(map[string]interface{}) + _ = Decode(&person, &result) + } +} diff --git a/mapstructure_examples_test.go b/mapstructure_examples_test.go index 2413b69..3880895 100644 --- a/mapstructure_examples_test.go +++ b/mapstructure_examples_test.go @@ -228,6 +228,41 @@ func ExampleDecode_remainingData() { // mapstructure.Person{Name:"Mitchell", Age:91, Other:map[string]interface {}{"email":"mitchell@example.com"}} } +func ExampleDecode_remainingDataDecodeBackToMapInFlatFormat() { + // Note that the mapstructure tags defined in the struct type + // can indicate which fields the values are mapped to. + type Person struct { + Name string + Age int + Other map[string]interface{} `mapstructure:",remain"` + } + + input := map[string]interface{}{ + "name": "Luffy", + "age": 19, + "powers": []string{ + "Rubber Man", + "Conqueror Haki", + }, + } + + var person Person + err := Decode(input, &person) + if err != nil { + panic(err) + } + + result := make(map[string]interface{}) + err = Decode(&person, &result) + if err != nil { + panic(err) + } + + fmt.Printf("%#v", result) + // Output: + // map[string]interface {}{"Age":19, "Name":"Luffy", "powers":[]string{"Rubber Man", "Conqueror Haki"}} +} + func ExampleDecode_omitempty() { // Add omitempty annotation to avoid map keys for empty values type Family struct { diff --git a/mapstructure_test.go b/mapstructure_test.go index 94a9e40..7d86acd 100644 --- a/mapstructure_test.go +++ b/mapstructure_test.go @@ -2247,6 +2247,38 @@ func TestDecodeTable(t *testing.T) { &map[string]interface{}{"visible": nil}, false, }, + { + "remainder with decode to map", + &Remainder{ + A: "Alabasta", + Extra: map[string]interface{}{ + "B": "Baratie", + "C": "Cocoyasi", + }, + }, + &map[string]interface{}{}, + &map[string]interface{}{ + "A": "Alabasta", + "B": "Baratie", + "C": "Cocoyasi", + }, + false, + }, + { + "remainder with decode to map with non-map field", + &struct { + A string + Extra *struct{} `mapstructure:",remain"` + }{ + A: "Alabasta", + Extra: nil, + }, + &map[string]interface{}{}, + &map[string]interface{}{ + "A": "Alabasta", + }, + true, + }, } for _, tt := range tests {