From 34b1dacdec4308596d00619380e322b0b1ce6690 Mon Sep 17 00:00:00 2001 From: team-humaki Date: Thu, 17 Sep 2026 14:27:12 -0700 Subject: [PATCH] struct: reflect []rune as a string --- struct.go | 23 ++++++++++++++++++++++- struct_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/struct.go b/struct.go index 819a6e5..72441fa 100644 --- a/struct.go +++ b/struct.go @@ -79,6 +79,18 @@ var reflectTime = reflect.TypeOf(time.Now()).Kind() // setSliceWithProperType sets proper values to slice based on its type. func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error { + sliceOf := field.Type().Elem().Kind() + // []rune (and []int32) round-trip as a Unicode string, not a delimited list. + if sliceOf == reflect.Int32 { + runes := []rune(key.String()) + slice := reflect.MakeSlice(field.Type(), len(runes), len(runes)) + for i, r := range runes { + slice.Index(i).SetInt(int64(r)) + } + field.Set(slice) + return nil + } + var strs []string if allowShadow { strs = key.StringsWithShadows(delim) @@ -94,7 +106,6 @@ func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowSh var vals interface{} var err error - sliceOf := field.Type().Elem().Kind() switch sliceOf { case reflect.String: vals = strs @@ -463,6 +474,16 @@ func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, all } sliceOf := field.Type().Elem().Kind() + // []rune / []int32: write the code points as a string so ReflectFrom/MapTo round-trip. + if sliceOf == reflect.Int32 { + runes := make([]rune, field.Len()) + for i := 0; i < field.Len(); i++ { + runes[i] = rune(slice.Index(i).Int()) + } + key.SetValue(string(runes)) + return nil + } + if allowShadow { var keyWithShadows *Key for i := 0; i < field.Len(); i++ { diff --git a/struct_test.go b/struct_test.go index ad6ad04..d9f0e92 100644 --- a/struct_test.go +++ b/struct_test.go @@ -910,3 +910,33 @@ Title = Consultant Engineer ) }) } + +func Test_ReflectFromAndMapToRuneSlice(t *testing.T) { + type Example struct { + Prop1 []rune `ini:"prop.1"` + } + + t.Run("reflect []rune as a string", func(t *testing.T) { + cfg := Empty() + require.NoError(t, ReflectFrom(cfg, &Example{Prop1: []rune("abc")})) + assert.Equal(t, "abc", cfg.Section("").Key("prop.1").String()) + + var buf bytes.Buffer + _, err := cfg.WriteTo(&buf) + require.NoError(t, err) + assert.Contains(t, buf.String(), "prop.1 = abc") + }) + + t.Run("map []rune from a string and round-trip", func(t *testing.T) { + cfg, err := Load([]byte("prop.1 = café")) + require.NoError(t, err) + + got := new(Example) + require.NoError(t, cfg.MapTo(got)) + assert.Equal(t, []rune("café"), got.Prop1) + + out := Empty() + require.NoError(t, ReflectFrom(out, got)) + assert.Equal(t, "café", out.Section("").Key("prop.1").String()) + }) +}