Skip to content

Commit

Permalink
Merge pull request #8 from go-viper/decode-remaining-fields
Browse files Browse the repository at this point in the history
Fix Encoding Struct Back to Map Bug (mitchellh#279)
  • Loading branch information
sagikazarmark authored Dec 18, 2023
2 parents 9b573a2 + cd3404e commit 8110e71
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 0 deletions.
12 changes: 12 additions & 0 deletions mapstructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 26 additions & 0 deletions mapstructure_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
35 changes: 35 additions & 0 deletions mapstructure_examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,41 @@ func ExampleDecode_remainingData() {
// mapstructure.Person{Name:"Mitchell", Age:91, Other:map[string]interface {}{"email":"[email protected]"}}
}

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 {
Expand Down
32 changes: 32 additions & 0 deletions mapstructure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 8110e71

Please sign in to comment.