Skip to content

Commit

Permalink
Fix mitchellhGH-340: Decoding array of slices causes panic
Browse files Browse the repository at this point in the history
  • Loading branch information
nolag committed Oct 30, 2023
1 parent bf980b3 commit 50da267
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 59 deletions.
118 changes: 59 additions & 59 deletions mapstructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,84 +9,84 @@
//
// The simplest function to start with is Decode.
//
// Field Tags
// # Field Tags
//
// When decoding to a struct, mapstructure will use the field name by
// default to perform the mapping. For example, if a struct has a field
// "Username" then mapstructure will look for a key in the source value
// of "username" (case insensitive).
//
// type User struct {
// Username string
// }
// type User struct {
// Username string
// }
//
// You can change the behavior of mapstructure by using struct tags.
// The default struct tag that mapstructure looks for is "mapstructure"
// but you can customize it using DecoderConfig.
//
// Renaming Fields
// # Renaming Fields
//
// To rename the key that mapstructure looks for, use the "mapstructure"
// tag and set a value directly. For example, to change the "username" example
// above to "user":
//
// type User struct {
// Username string `mapstructure:"user"`
// }
// type User struct {
// Username string `mapstructure:"user"`
// }
//
// Embedded Structs and Squashing
// # Embedded Structs and Squashing
//
// Embedded structs are treated as if they're another field with that name.
// By default, the two structs below are equivalent when decoding with
// mapstructure:
//
// type Person struct {
// Name string
// }
// type Person struct {
// Name string
// }
//
// type Friend struct {
// Person
// }
// type Friend struct {
// Person
// }
//
// type Friend struct {
// Person Person
// }
// type Friend struct {
// Person Person
// }
//
// This would require an input that looks like below:
//
// map[string]interface{}{
// "person": map[string]interface{}{"name": "alice"},
// }
// map[string]interface{}{
// "person": map[string]interface{}{"name": "alice"},
// }
//
// If your "person" value is NOT nested, then you can append ",squash" to
// your tag value and mapstructure will treat it as if the embedded struct
// were part of the struct directly. Example:
//
// type Friend struct {
// Person `mapstructure:",squash"`
// }
// type Friend struct {
// Person `mapstructure:",squash"`
// }
//
// Now the following input would be accepted:
//
// map[string]interface{}{
// "name": "alice",
// }
// map[string]interface{}{
// "name": "alice",
// }
//
// When decoding from a struct to a map, the squash tag squashes the struct
// fields into a single map. Using the example structs from above:
//
// Friend{Person: Person{Name: "alice"}}
// Friend{Person: Person{Name: "alice"}}
//
// Will be decoded into a map:
//
// map[string]interface{}{
// "name": "alice",
// }
// map[string]interface{}{
// "name": "alice",
// }
//
// DecoderConfig has a field that changes the behavior of mapstructure
// to always squash embedded structs.
//
// Remainder Values
// # Remainder Values
//
// If there are any unmapped keys in the source value, mapstructure by
// default will silently ignore them. You can error by setting ErrorUnused
Expand All @@ -98,20 +98,20 @@
// probably be a "map[string]interface{}" or "map[interface{}]interface{}".
// See example below:
//
// type Friend struct {
// Name string
// Other map[string]interface{} `mapstructure:",remain"`
// }
// type Friend struct {
// Name string
// Other map[string]interface{} `mapstructure:",remain"`
// }
//
// Given the input below, Other would be populated with the other
// values that weren't used (everything but "name"):
//
// map[string]interface{}{
// "name": "bob",
// "address": "123 Maple St.",
// }
// map[string]interface{}{
// "name": "bob",
// "address": "123 Maple St.",
// }
//
// Omit Empty Values
// # Omit Empty Values
//
// When decoding from a struct to any other value, you may use the
// ",omitempty" suffix on your tag to omit that value if it equates to
Expand All @@ -122,37 +122,37 @@
// field value is zero and a numeric type, the field is empty, and it won't
// be encoded into the destination type.
//
// type Source struct {
// Age int `mapstructure:",omitempty"`
// }
// type Source struct {
// Age int `mapstructure:",omitempty"`
// }
//
// Unexported fields
// # Unexported fields
//
// Since unexported (private) struct fields cannot be set outside the package
// where they are defined, the decoder will simply skip them.
//
// For this output type definition:
//
// type Exported struct {
// private string // this unexported field will be skipped
// Public string
// }
// type Exported struct {
// private string // this unexported field will be skipped
// Public string
// }
//
// Using this map as input:
//
// map[string]interface{}{
// "private": "I will be ignored",
// "Public": "I made it through!",
// }
// map[string]interface{}{
// "private": "I will be ignored",
// "Public": "I made it through!",
// }
//
// The following struct will be decoded:
//
// type Exported struct {
// private: "" // field is left with an empty string (zero value)
// Public: "I made it through!"
// }
// type Exported struct {
// private: "" // field is left with an empty string (zero value)
// Public: "I made it through!"
// }
//
// Other Configuration
// # Other Configuration
//
// mapstructure is highly configurable. See the DecoderConfig struct
// for other features and options that are supported.
Expand Down Expand Up @@ -1163,7 +1163,7 @@ func (d *Decoder) decodeArray(name string, data interface{}, val reflect.Value)

valArray := val

if valArray.Interface() == reflect.Zero(valArray.Type()).Interface() || d.config.ZeroFields {
if valArray.Comparable() && valArray.Interface() == reflect.Zero(valArray.Type()).Interface() || d.config.ZeroFields {
// Check input type
if dataValKind != reflect.Array && dataValKind != reflect.Slice {
if d.config.WeaklyTypedInput {
Expand Down
16 changes: 16 additions & 0 deletions mapstructure_bugs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -625,3 +625,19 @@ func TestMapOmitEmptyWithEmptyFieldnameInTag(t *testing.T) {
t.Fatalf("fail: %#v", m)
}
}

// GH-340: Decoding array of slices causes panic
type HasNonComparableType struct {
NonComparableType [2][]byte
}

func TestDecode_nonComparableType(t *testing.T) {
decodeTo := &HasNonComparableType{}
expected := [2][]byte{{1, 2}, {3, 4, 5}}
if err := Decode(map[string]interface{}{"NonComparableType": expected}, &decodeTo); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expected, decodeTo.NonComparableType) {
t.Fatalf("fail: %#v", decodeTo.NonComparableType)
}
}

0 comments on commit 50da267

Please sign in to comment.