Skip to content

Commit 2332ee2

Browse files
committed
fix: cycle ref
1 parent bc43df2 commit 2332ee2

File tree

2 files changed

+89
-43
lines changed

2 files changed

+89
-43
lines changed

core/conf/config.go

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func LoadConfig(file string, v any, opts ...Option) error {
7777

7878
// LoadFromJsonBytes loads config into v from content json bytes.
7979
func LoadFromJsonBytes(content []byte, v any) error {
80-
info, err := buildFieldsInfo(reflect.TypeOf(v), "")
80+
info, err := buildFieldsInfo(reflect.TypeOf(v), "", make(map[reflect.Type]*fieldInfo))
8181
if err != nil {
8282
return err
8383
}
@@ -152,10 +152,11 @@ func addOrMergeFields(info *fieldInfo, key string, child *fieldInfo, fullName st
152152
return nil
153153
}
154154

155-
func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, fullName string) error {
155+
func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type,
156+
fullName string, visited map[reflect.Type]*fieldInfo) error {
156157
switch ft.Kind() {
157158
case reflect.Struct:
158-
fields, err := buildFieldsInfo(ft, fullName)
159+
fields, err := buildFieldsInfo(ft, fullName, visited)
159160
if err != nil {
160161
return err
161162
}
@@ -166,7 +167,7 @@ func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.T
166167
}
167168
}
168169
case reflect.Map:
169-
elemField, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName)
170+
elemField, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName, visited)
170171
if err != nil {
171172
return err
172173
}
@@ -192,14 +193,44 @@ func buildAnonymousFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.T
192193
return nil
193194
}
194195

195-
func buildFieldsInfo(tp reflect.Type, fullName string) (*fieldInfo, error) {
196+
func buildFieldsInfo(tp reflect.Type, fullName string,
197+
visited map[reflect.Type]*fieldInfo) (*fieldInfo, error) {
196198
tp = mapping.Deref(tp)
199+
if finfo, ok := visited[tp]; ok {
200+
return finfo, nil
201+
}
197202

198203
switch tp.Kind() {
199204
case reflect.Struct:
200-
return buildStructFieldsInfo(tp, fullName)
205+
info := &fieldInfo{
206+
children: make(map[string]*fieldInfo),
207+
}
208+
visited[tp] = info
209+
210+
for i := 0; i < tp.NumField(); i++ {
211+
field := tp.Field(i)
212+
if !field.IsExported() {
213+
continue
214+
}
215+
216+
name := getTagName(field)
217+
lowerCaseName := toLowerCase(name)
218+
ft := mapping.Deref(field.Type)
219+
// flatten anonymous fields
220+
if field.Anonymous {
221+
if err := buildAnonymousFieldInfo(info, lowerCaseName, ft,
222+
getFullName(fullName, lowerCaseName), visited); err != nil {
223+
return nil, err
224+
}
225+
} else if err := buildNamedFieldInfo(info, lowerCaseName, ft,
226+
getFullName(fullName, lowerCaseName), visited); err != nil {
227+
return nil, err
228+
}
229+
}
230+
231+
return info, nil
201232
case reflect.Array, reflect.Slice, reflect.Map:
202-
return buildFieldsInfo(mapping.Deref(tp.Elem()), fullName)
233+
return buildFieldsInfo(mapping.Deref(tp.Elem()), fullName, visited)
203234
case reflect.Chan, reflect.Func:
204235
return nil, fmt.Errorf("unsupported type: %s, fullName: %s", tp.Kind(), fullName)
205236
default:
@@ -209,23 +240,24 @@ func buildFieldsInfo(tp reflect.Type, fullName string) (*fieldInfo, error) {
209240
}
210241
}
211242

212-
func buildNamedFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type, fullName string) error {
243+
func buildNamedFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type,
244+
fullName string, visited map[reflect.Type]*fieldInfo) error {
213245
var finfo *fieldInfo
214246
var err error
215247

216248
switch ft.Kind() {
217249
case reflect.Struct:
218-
finfo, err = buildFieldsInfo(ft, fullName)
250+
finfo, err = buildFieldsInfo(ft, fullName, visited)
219251
if err != nil {
220252
return err
221253
}
222254
case reflect.Array, reflect.Slice:
223-
finfo, err = buildFieldsInfo(ft.Elem(), fullName)
255+
finfo, err = buildFieldsInfo(ft.Elem(), fullName, visited)
224256
if err != nil {
225257
return err
226258
}
227259
case reflect.Map:
228-
elemInfo, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName)
260+
elemInfo, err := buildFieldsInfo(mapping.Deref(ft.Elem()), fullName, visited)
229261
if err != nil {
230262
return err
231263
}
@@ -235,7 +267,7 @@ func buildNamedFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type,
235267
mapField: elemInfo,
236268
}
237269
default:
238-
finfo, err = buildFieldsInfo(ft, fullName)
270+
finfo, err = buildFieldsInfo(ft, fullName, visited)
239271
if err != nil {
240272
return err
241273
}
@@ -244,35 +276,6 @@ func buildNamedFieldInfo(info *fieldInfo, lowerCaseName string, ft reflect.Type,
244276
return addOrMergeFields(info, lowerCaseName, finfo, fullName)
245277
}
246278

247-
func buildStructFieldsInfo(tp reflect.Type, fullName string) (*fieldInfo, error) {
248-
info := &fieldInfo{
249-
children: make(map[string]*fieldInfo),
250-
}
251-
252-
for i := 0; i < tp.NumField(); i++ {
253-
field := tp.Field(i)
254-
if !field.IsExported() {
255-
continue
256-
}
257-
258-
name := getTagName(field)
259-
lowerCaseName := toLowerCase(name)
260-
ft := mapping.Deref(field.Type)
261-
// flatten anonymous fields
262-
if field.Anonymous {
263-
if err := buildAnonymousFieldInfo(info, lowerCaseName, ft,
264-
getFullName(fullName, lowerCaseName)); err != nil {
265-
return nil, err
266-
}
267-
} else if err := buildNamedFieldInfo(info, lowerCaseName, ft,
268-
getFullName(fullName, lowerCaseName)); err != nil {
269-
return nil, err
270-
}
271-
}
272-
273-
return info, nil
274-
}
275-
276279
// getTagName get the tag name of the given field, if no tag name, use file.Name.
277280
// field.Name is returned on tags like `json:""` and `json:",optional"`.
278281
func getTagName(field reflect.StructField) string {

core/conf/config_test.go

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,7 @@ func TestLoadNamedFieldOverwritten(t *testing.T) {
984984
})
985985

986986
t.Run("overwritten named *struct", func(t *testing.T) {
987-
type (
987+
type (
988988
Elem string
989989
Named struct {
990990
Elem string
@@ -1328,7 +1328,7 @@ func Test_buildFieldsInfo(t *testing.T) {
13281328

13291329
for _, tt := range tests {
13301330
t.Run(tt.name, func(t *testing.T) {
1331-
_, err := buildFieldsInfo(tt.t, "")
1331+
_, err := buildFieldsInfo(tt.t, "", make(map[reflect.Type]*fieldInfo))
13321332
if tt.ok {
13331333
assert.NoError(t, err)
13341334
} else {
@@ -1339,6 +1339,49 @@ func Test_buildFieldsInfo(t *testing.T) {
13391339
}
13401340
}
13411341

1342+
func TestLoadWithCycleReference(t *testing.T) {
1343+
type Node struct {
1344+
Name string `json:"name"`
1345+
Children []*Node `json:"children,optional"`
1346+
Parent *Node `json:"parent,optional"` // 指向父节点的指针
1347+
}
1348+
1349+
var c Node
1350+
input := []byte(`
1351+
name: root
1352+
children:
1353+
- name: child1
1354+
children:
1355+
- name: grandchild1
1356+
- name: child2
1357+
`)
1358+
err := LoadFromYamlBytes(input, &c)
1359+
assert.NoError(t, err)
1360+
1361+
// 手动回填父指针以构建完整的双向树
1362+
var fillParent func(n *Node)
1363+
fillParent = func(n *Node) {
1364+
for _, child := range n.Children {
1365+
child.Parent = n
1366+
fillParent(child)
1367+
}
1368+
}
1369+
fillParent(&c)
1370+
1371+
assert.Equal(t, "root", c.Name)
1372+
assert.Len(t, c.Children, 2)
1373+
assert.Equal(t, "child1", c.Children[0].Name)
1374+
assert.Equal(t, "child2", c.Children[1].Name)
1375+
assert.Len(t, c.Children[0].Children, 1)
1376+
assert.Equal(t, "grandchild1", c.Children[0].Children[0].Name)
1377+
1378+
// 验证父指针
1379+
assert.NotNil(t, c.Children[0].Parent)
1380+
assert.Equal(t, "root", c.Children[0].Parent.Name)
1381+
assert.NotNil(t, c.Children[0].Children[0].Parent)
1382+
assert.Equal(t, "child1", c.Children[0].Children[0].Parent.Name)
1383+
}
1384+
13421385
func createTempFile(t *testing.T, ext, text string) (string, error) {
13431386
tmpFile, err := os.CreateTemp(os.TempDir(), hash.Md5Hex([]byte(text))+"*"+ext)
13441387
if err != nil {

0 commit comments

Comments
 (0)