@@ -77,7 +77,7 @@ func LoadConfig(file string, v any, opts ...Option) error {
7777
7878// LoadFromJsonBytes loads config into v from content json bytes.
7979func 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"`.
278281func getTagName (field reflect.StructField ) string {
0 commit comments