commit ccf1f90ce50f719925365caca31c9932a7869eaf
Author: hhvn <dev@hhvn.uk>
Date: Sun, 11 Feb 2024 20:41:53 +0000
Init
Diffstat:
6 files changed, 254 insertions(+), 0 deletions(-)
diff --git a/go.mod b/go.mod
@@ -0,0 +1,5 @@
+module hhvn.uk/go-superstruct
+
+go 1.21.5
+
+require github.com/jinzhu/copier v0.4.0 // indirect
diff --git a/go.sum b/go.sum
@@ -0,0 +1,2 @@
+github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
+github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
diff --git a/superstruct.go b/superstruct.go
@@ -0,0 +1,152 @@
+// Package superstruct uses reflection to merge multiple structs into one.
+//
+// The resulting struct's fields cannot be accessed without reflection.
+//
+// The intended use of this package is to quickly whack together structures for
+// json.Marshal. As json.Marshal uses reflection under the hood, a superstruct
+// can be passed to it like any other struct.
+package superstruct
+
+import (
+ "fmt"
+ "sort"
+ "errors"
+ "strings"
+ "reflect"
+)
+
+var (
+ ErrNotStruct = errors.New("not a structure")
+)
+
+func copyWalk(vdst, src reflect.Value) {
+ dst := vdst.Elem()
+
+ switch dst.Kind() {
+ case reflect.Bool: fallthrough
+ case reflect.Int: fallthrough
+ case reflect.Int8: fallthrough
+ case reflect.Int16: fallthrough
+ case reflect.Int32: fallthrough
+ case reflect.Int64: fallthrough
+ case reflect.Uint: fallthrough
+ case reflect.Uint8: fallthrough
+ case reflect.Uint16: fallthrough
+ case reflect.Uint32: fallthrough
+ case reflect.Uint64: fallthrough
+ case reflect.Float32: fallthrough
+ case reflect.Float64: fallthrough
+ case reflect.Array: fallthrough
+ case reflect.Slice:
+ dst.Set(src)
+
+ case reflect.String:
+ dst.SetString(src.String())
+
+ case reflect.Map:
+ keys := src.MapKeys()
+ dst.Set(reflect.MakeMapWithSize(dst.Type(), len(keys)))
+
+ for _, k := range keys {
+ dst.SetMapIndex(k, src.MapIndex(k))
+ }
+
+ case reflect.Struct:
+ for _, f := range reflect.VisibleFields(src.Type()) {
+ switch dst.FieldByName(f.Name).Kind() {
+ case reflect.Pointer: fallthrough
+ case reflect.Interface:
+ d := dst.FieldByName(f.Name)
+ s := src.FieldByName(f.Name)
+ d.Set(s)
+ default:
+ d := dst.FieldByName(f.Name).Addr()
+ s := src.FieldByName(f.Name)
+ copyWalk(d, s)
+ }
+ }
+ }
+}
+
+func do(cp bool, in... interface{}) (interface{}, error) {
+ v := make([]reflect.Value, len(in))
+ for i := range in {
+ v[i] = reflect.ValueOf(in[i])
+ if v[i].Kind() != reflect.Struct {
+ return nil, ErrNotStruct
+ }
+ }
+
+ t := make([]reflect.Type, len(in))
+ for i := range in {
+ t[i] = v[i].Type()
+ }
+
+ f := make([][]reflect.StructField, len(in))
+ for i := range in {
+ f[i] = reflect.VisibleFields(t[i])
+ }
+
+ fm := make(map[string]reflect.StructField)
+
+ for i := range in {
+ for _, v := range f[i] {
+ fm[v.Name] = v
+ }
+ }
+
+ fs := make([]reflect.StructField, len(fm))
+
+ i := 0
+ for _, v := range fm {
+ fs[i] = v
+ i++
+ }
+
+ rv := reflect.New(reflect.StructOf(fs))
+
+ if cp {
+ for i := range in {
+ copyWalk(rv, v[i])
+ }
+ }
+
+ return rv.Elem().Interface(), nil
+}
+
+// Create combines multiple structs into a single struct and returns it ready to use.
+func Create(i... interface{}) (interface{}, error) {
+ return do(false, i...)
+}
+
+// CreateAndCopy also combines multiple structs into a single struct,
+// but copies the values in for you. The fields from structs passed later
+// take precedence over those passed earlier.
+func CreateAndCopy(i... interface{}) (interface{}, error) {
+ return do(true, i...)
+}
+
+// SortedStructString produces a string representation of the fields and values
+// of a struct. Each field is printed on its own line. Lines are sorted with
+// sort.Strings()
+//
+// The main use for this function is in creating a deterministic output for the
+// test and example included with this package, though it is exported anyway
+// for convenience.
+func SortedStructString(s interface{}) string {
+ var lines []string
+
+ vs := reflect.ValueOf(s)
+
+ for _, v := range reflect.VisibleFields(vs.Type()) {
+ f := vs.FieldByName(v.Name)
+ if f.Kind() == reflect.Pointer || f.Kind() == reflect.Interface {
+ f = f.Elem()
+ }
+ lines = append(lines, fmt.Sprintf("%v: %+v", v.Name, f.Interface()))
+ }
+
+ sort.Strings(lines)
+
+ return strings.Join(lines, "\n")
+}
diff --git a/superstruct_example.go b/superstruct_example.go
@@ -0,0 +1,31 @@
+package superstruct
+
+import (
+ "fmt"
+)
+
+type First struct {
+ A string
+ C string
+}
+
+type Second struct {
+ B string
+ C string
+}
+
+
+func Example() {
+ a := First{"a", "c"}
+ b := Second{"b", "d"}
+ c, err := CreateAndCopy(a, b)
+ if err != nil {
+ return
+ }
+
+ fmt.Println(SortedStructString(c))
+ // Output:
+ // A: a
+ // B: b
+ // C: d
+}
diff --git a/superstruct_test.go b/superstruct_test.go
@@ -0,0 +1,53 @@
+package superstruct
+
+import (
+ "strings"
+ "testing"
+
+ _ "embed"
+)
+
+type A struct {
+ A string
+ C string
+}
+
+type B struct {
+ B string
+ C string
+ D *string
+ E interface{}
+ F int16
+ G uint32
+ H float64
+ I [5]int
+ J []int
+ K map[string]int
+}
+
+//go:embed test_output.txt
+var expected string
+
+func Test(t *testing.T) {
+ m := make(map[string]int)
+ m["l"] = 99
+ m["m"] = 98
+
+ str := "5"
+ arr := [5]int{10, 9, 8, 7, 6}
+ sl := []int{3, 2}
+ a := A{"1", "2"}
+ b := B{"3", "4", &str, "hi", 16, 32, 64.0, arr, sl, m}
+
+ c, err := CreateAndCopy(a, b)
+ if err != nil {
+ t.Fatalf("%v", err)
+ }
+
+ expected = strings.TrimSuffix(expected, "\n")
+ got := SortedStructString(c)
+
+ if expected != got {
+ t.Fatalf("\nExpected:\n%v\nGot:\n%v", expected, got)
+ }
+}
diff --git a/test_output.txt b/test_output.txt
@@ -0,0 +1,11 @@
+A: 1
+B: 3
+C: 4
+D: 5
+E: hi
+F: 16
+G: 32
+H: 64
+I: [10 9 8 7 6]
+J: [3 2]
+K: map[l:99 m:98]