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

Fix Encoding Struct Back to Map Bug (#279) #307

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
12 changes: 12 additions & 0 deletions mapstructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,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 @@ -2245,6 +2245,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