如何在没有嵌套循环的情况下将嵌套结构形成为另一个?

时间:2017-06-08 17:41:25

标签: go

如何将A和B的选定值注入以下儿童C?

decoder.go Playground link

package main

import (
    "fmt"
)

type Input struct {
    A []A
}

type A struct {
    AID int
    B []B
}

type B struct {
    BID int
    C []C
}

type C struct {
    // I want to inject only AID and BID here
    // But, without injecting A and B directly
    //   (without recursively)
    CID int
}

func main() {
    res := Input{
        A: []A {
            A {
                AID: 1,
                B: []B {
                    B{ BID: 11, C: []C{{ 111 }, { 111 }}},
                    B{ BID: 12, C: []C{{ 121 }, { 122 }}},
                },
            },
            A {
                AID: 2,
                B: []B {
                    B{ BID: 21, C: []C{{ 211 }, { 211 }}},
                    B{ BID: 22, C: []C{{ 221 }, { 222 }}},
                },
            },
        },
    }

    // I want to inject AID and BID into C
    // WITHOUT nested loops like this:
    for _, a := range res.A {
        for _, b := range a.B {
            for _, c := range b.C {
                fmt.Println(a.AID, b.BID, c.CID)
            }
        }
    }
}

2 个答案:

答案 0 :(得分:1)

如果您不想使用嵌套循环,则一种解决方案是使用递归调用和反射将属性/属性注入到结构中。在以下实现中,要注入的属性/属性包含在结构实现Injectable接口中。可以在Go Playground找到工作示例。

  1. 定义界面。

    type Injectable interface {
        InjectTo(v interface{})
    }
    
  2. 定义包含要注入的属性/属性的数据结构,例如

    type Property struct {
        AID int
        BID int
    }
    
    type C struct {
        // The properties will be injected here
        Property
        CID int
    }
    
  3. 使用反射和递归调用实现InjectTo

    //Method must be pointer receiver since p will be used 
    //as temporary placeholder for parent properties/attributes.
    func (p *Property) injectRecursive(v reflect.Value, it reflect.Type, pv reflect.Value) {
        switch v.Kind() {
        case reflect.Struct:
            vt := v.Type()
            //Embedded struct is a 'value' type implement Injectable
            if vt.Implements(it) {
                //Inject value to embedded struct
                ot := pv.Type()
                for k := 0; k < pv.NumField(); k++ {
                    name := ot.Field(k).Name
                    f := v.FieldByName(name)
                    if f.CanSet() {
                        f.Set(pv.Field(k))
                    }
                }
            } else {
                for k := 0; k < v.NumField(); k++ {
                    fv := v.Field(k)
    
                    //Match by field name.
                    //For more robust and generic solution
                    //consider using other approach, e.g. tag
                    f := pv.FieldByName(vt.Field(k).Name)
                    if f.CanSet() {
                        f.Set(fv)
                    } else {
                        p.injectRecursive(fv, it, pv)
                    }
                }
            }
        case reflect.Slice, reflect.Array:
            for k := 0; k < v.Len(); k++ {
                p.injectRecursive(v.Index(k), it, pv)
            }
        case reflect.Ptr:
            if v.IsValid() {
                p.injectRecursive(v.Elem(), it, pv)
            }
        }
    }
    
    //InjectTo must be Value (not pointer) receiver
    func (p Property) InjectTo(s interface{}) {
        sv := reflect.Indirect(reflect.ValueOf(s))
        pv := reflect.Indirect(reflect.ValueOf(&p))
        it := reflect.TypeOf((*Injectable)(nil)).Elem()
        p.injectRecursive(sv, it, pv)
    }
    
  4. 您可以通过以下方式注入属性:

    res := Input{...}
    prop := Property{}
    prop.InjectTo(&res)
    

答案 1 :(得分:0)

Go通常鼓励使用显式方法,并且嵌套for循环在这里实际发生的事情上是非常明确的。可能没有必要尝试找到更简洁的解决方案,Go可能不会提供它。