diff --git a/struct.go b/struct.go index 819a6e5..6495ccd 100644 --- a/struct.go +++ b/struct.go @@ -94,7 +94,13 @@ func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowSh var vals interface{} var err error - sliceOf := field.Type().Elem().Kind() + elemType := field.Type().Elem() + isPtrElem := elemType.Kind() == reflect.Pointer + valueType := elemType + if isPtrElem { + valueType = elemType.Elem() + } + sliceOf := valueType.Kind() switch sliceOf { case reflect.String: vals = strs @@ -113,7 +119,7 @@ func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowSh case reflectTime: vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false) default: - return fmt.Errorf("unsupported type '[]%s'", sliceOf) + return fmt.Errorf("unsupported type '[]%s'", field.Type().Elem().Kind()) } if err != nil && isStrict { return err @@ -121,23 +127,32 @@ func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowSh slice := reflect.MakeSlice(field.Type(), numVals, numVals) for i := 0; i < numVals; i++ { + var v interface{} switch sliceOf { case reflect.String: - slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i])) + v = vals.([]string)[i] case reflect.Int: - slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i])) + v = vals.([]int)[i] case reflect.Int64: - slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i])) + v = vals.([]int64)[i] case reflect.Uint: - slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i])) + v = vals.([]uint)[i] case reflect.Uint64: - slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i])) + v = vals.([]uint64)[i] case reflect.Float64: - slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i])) + v = vals.([]float64)[i] case reflect.Bool: - slice.Index(i).Set(reflect.ValueOf(vals.([]bool)[i])) + v = vals.([]bool)[i] case reflectTime: - slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i])) + v = vals.([]time.Time)[i] + } + rv := reflect.ValueOf(v) + if isPtrElem { + pv := reflect.New(rv.Type()) + pv.Elem().Set(rv) + slice.Index(i).Set(pv) + } else { + slice.Index(i).Set(rv) } } field.Set(slice) diff --git a/struct_test.go b/struct_test.go index ad6ad04..825e6e6 100644 --- a/struct_test.go +++ b/struct_test.go @@ -724,6 +724,46 @@ AllowedIPs = 10.2.0.6/32,fd00:2::6/128 }) } +func TestMapToPointerSlices(t *testing.T) { + t.Run("map allowshadow keys to []*string", func(t *testing.T) { + type Config struct { + Users []*string `ini:"user,allowshadow"` + } + + f, err := LoadSources(LoadOptions{AllowShadows: true}, []byte(`user=foo +user=bar +user=baz +`)) + require.NoError(t, err) + + cfg := new(Config) + require.NoError(t, f.MapTo(cfg)) + require.Len(t, cfg.Users, 3) + require.NotNil(t, cfg.Users[0]) + require.NotNil(t, cfg.Users[1]) + require.NotNil(t, cfg.Users[2]) + assert.Equal(t, "foo", *cfg.Users[0]) + assert.Equal(t, "bar", *cfg.Users[1]) + assert.Equal(t, "baz", *cfg.Users[2]) + }) + + t.Run("map delimited values to []*int", func(t *testing.T) { + type Config struct { + Nums []*int `delim:","` + } + + f, err := Load([]byte(`Nums=1,2,3`)) + require.NoError(t, err) + + cfg := new(Config) + require.NoError(t, f.MapTo(cfg)) + require.Len(t, cfg.Nums, 3) + assert.Equal(t, 1, *cfg.Nums[0]) + assert.Equal(t, 2, *cfg.Nums[1]) + assert.Equal(t, 3, *cfg.Nums[2]) + }) +} + // Inspired by https://github.com/go-ini/ini/issues/196 func TestMapToAndReflectFromStructWithShadows(t *testing.T) { t.Run("map to struct and then reflect with shadows should generate original config content", func(t *testing.T) {