superstruct.go (3957B)
1 // Package superstruct uses reflection to merge multiple structs into one. 2 // 3 // The resulting struct's fields cannot be accessed without reflection. 4 // 5 // The intended use of this package is to quickly whack together structures for 6 // json.Marshal. As json.Marshal uses reflection under the hood, a superstruct 7 // can be passed to it like any other struct. 8 package superstruct // import "hhvn.uk/go-superstruct" 9 10 import ( 11 "fmt" 12 "sort" 13 "errors" 14 "strings" 15 "reflect" 16 ) 17 18 var ( 19 ErrNotStruct = errors.New("not a structure") 20 ) 21 22 func getStruct(v reflect.Value) (reflect.Value, error) { 23 for (v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface) { 24 v = v.Elem() 25 } 26 27 if v.Kind() == reflect.Struct { 28 return v, nil 29 } else { 30 return v, ErrNotStruct 31 } 32 } 33 34 func copyWalk(vdst, src reflect.Value) { 35 dst := vdst.Elem() 36 37 switch dst.Kind() { 38 case reflect.Bool: fallthrough 39 case reflect.Int: fallthrough 40 case reflect.Int8: fallthrough 41 case reflect.Int16: fallthrough 42 case reflect.Int32: fallthrough 43 case reflect.Int64: fallthrough 44 case reflect.Uint: fallthrough 45 case reflect.Uint8: fallthrough 46 case reflect.Uint16: fallthrough 47 case reflect.Uint32: fallthrough 48 case reflect.Uint64: fallthrough 49 case reflect.Float32: fallthrough 50 case reflect.Float64: fallthrough 51 case reflect.Array: fallthrough 52 case reflect.Slice: 53 dst.Set(src) 54 55 case reflect.String: 56 dst.SetString(src.String()) 57 58 case reflect.Map: 59 keys := src.MapKeys() 60 dst.Set(reflect.MakeMapWithSize(dst.Type(), len(keys))) 61 62 for _, k := range keys { 63 dst.SetMapIndex(k, src.MapIndex(k)) 64 } 65 66 case reflect.Struct: 67 for _, f := range reflect.VisibleFields(src.Type()) { 68 switch dst.FieldByName(f.Name).Kind() { 69 case reflect.Pointer: fallthrough 70 case reflect.Interface: 71 d := dst.FieldByName(f.Name) 72 s := src.FieldByName(f.Name) 73 d.Set(s) 74 default: 75 d := dst.FieldByName(f.Name).Addr() 76 s := src.FieldByName(f.Name) 77 copyWalk(d, s) 78 } 79 } 80 } 81 } 82 83 func do(cp bool, in... interface{}) (interface{}, error) { 84 v := make([]reflect.Value, len(in)) 85 for i := range in { 86 var err error 87 v[i], err = getStruct(reflect.ValueOf(in[i])) 88 if err != nil { 89 return nil, err 90 } 91 } 92 93 t := make([]reflect.Type, len(in)) 94 for i := range in { 95 t[i] = v[i].Type() 96 } 97 98 f := make([][]reflect.StructField, len(in)) 99 for i := range in { 100 f[i] = reflect.VisibleFields(t[i]) 101 } 102 103 fm := make(map[string]reflect.StructField) 104 105 for i := range in { 106 for _, v := range f[i] { 107 fm[v.Name] = v 108 } 109 } 110 111 fs := make([]reflect.StructField, len(fm)) 112 113 i := 0 114 for _, v := range fm { 115 fs[i] = v 116 i++ 117 } 118 119 rv := reflect.New(reflect.StructOf(fs)) 120 121 if cp { 122 for i := range in { 123 copyWalk(rv, v[i]) 124 } 125 } 126 127 return rv.Elem().Interface(), nil 128 } 129 130 // Create combines multiple structs into a single struct and returns it ready to use. 131 func Create(i... interface{}) (interface{}, error) { 132 return do(false, i...) 133 } 134 135 // CreateAndCopy also combines multiple structs into a single struct, 136 // but copies the values in for you. The fields from structs passed later 137 // take precedence over those passed earlier. 138 func CreateAndCopy(i... interface{}) (interface{}, error) { 139 return do(true, i...) 140 } 141 142 // SortedStructString produces a string representation of the fields and values 143 // of a struct. Each field is printed on its own line. Lines are sorted with 144 // sort.Strings() 145 // 146 // The main use for this function is in creating a deterministic output for the 147 // test and example included with this package, though it is exported anyway 148 // for convenience. 149 func SortedStructString(s interface{}) string { 150 var lines []string 151 152 vs := reflect.ValueOf(s) 153 154 for _, v := range reflect.VisibleFields(vs.Type()) { 155 f := vs.FieldByName(v.Name) 156 if f.Kind() == reflect.Pointer || f.Kind() == reflect.Interface { 157 f = f.Elem() 158 } 159 lines = append(lines, fmt.Sprintf("%v: %+v", v.Name, f.Interface())) 160 } 161 162 sort.Strings(lines) 163 164 return strings.Join(lines, "\n") 165 }