go-superstruct

[library] uses reflection to merge structs
git clone https://hhvn.uk/go-superstruct
git clone git://hhvn.uk/go-superstruct
Log | Files | Refs | LICENSE

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 }