diff --git a/struct.go b/struct.go index 819a6e5..45864f7 100644 --- a/struct.go +++ b/struct.go @@ -278,9 +278,18 @@ func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bo // mapToField maps the given value to the matching field of the given section. // The sectionIndex is the index (if non unique sections are enabled) to which the value should be added. func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error { - if val.Kind() == reflect.Pointer { + for val.Kind() == reflect.Pointer { + if val.IsNil() { + if !val.CanSet() { + return fmt.Errorf("cannot initialize nil pointer: value is not settable") + } + val.Set(reflect.New(val.Type().Elem())) + } val = val.Elem() } + if !val.IsValid() { + return fmt.Errorf("cannot map to invalid value") + } typ := val.Type() for i := 0; i < typ.NumField(); i++ { diff --git a/struct_test.go b/struct_test.go index ad6ad04..ac12308 100644 --- a/struct_test.go +++ b/struct_test.go @@ -385,6 +385,50 @@ names=alice, bruce`)) }) } +// Regression test for https://github.com/go-ini/ini/issues/370: +// Mapping non-unique sections into a slice of struct pointers ([]*T) must +// not panic. +func Test_MapToSliceOfStructPointers(t *testing.T) { + const data = `[Peer] +PublicKey = key1 +[Peer] +PublicKey = key2 +` + type Peer struct { + PublicKey string `ini:"PublicKey"` + } + type Config struct { + Peers []*Peer `ini:"Peer,nonunique"` + } + f, err := LoadSources(LoadOptions{AllowNonUniqueSections: true}, []byte(data)) + assert.NoError(t, err) + cfg := new(Config) + assert.NoError(t, f.MapTo(cfg)) + assert.Len(t, cfg.Peers, 2) + if len(cfg.Peers) == 2 { + assert.Equal(t, "key1", cfg.Peers[0].PublicKey) + assert.Equal(t, "key2", cfg.Peers[1].PublicKey) + } +} + +// Regression test for https://github.com/go-ini/ini/issues/369 and #370: +// MapTo with a nil struct pointer must not panic; it should auto-initialize +// the pointer (analogous to encoding/json behaviour) and populate the struct. +func Test_MapToNilPointer(t *testing.T) { + f, err := Load([]byte("user = alice\npass = secret\n")) + assert.NoError(t, err) + + type S struct { + User string `ini:"user"` + Pass string `ini:"pass"` + } + var s *S + assert.NoError(t, f.MapTo(&s), "MapTo with nil pointer should not return an error") + assert.NotNil(t, s, "nil pointer should have been initialized by MapTo") + assert.Equal(t, "alice", s.User) + assert.Equal(t, "secret", s.Pass) +} + func Test_MapToStructNonUniqueSections(t *testing.T) { t.Run("map to struct non unique", func(t *testing.T) { t.Run("map file to struct non unique", func(t *testing.T) {