Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions mapstructure.go
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
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
Expand Up @@ -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 {
Expand Down
32 changes: 32 additions & 0 deletions mapstructure_test.go
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