Skip to content

Instantly share code, notes, and snippets.

@moycat
Created April 13, 2020 15:14
Show Gist options
  • Save moycat/16895539e2a4dfe9cfcc6b4a4369efca to your computer and use it in GitHub Desktop.
Save moycat/16895539e2a4dfe9cfcc6b4a4369efca to your computer and use it in GitHub Desktop.
Copy contents to same-name fields of another kind of struct, with auto marshaling & unmarshaling.
package main
import (
"encoding/json"
"errors"
"fmt"
"reflect"
)
type N struct {
Ha string
Ok bool
}
type A struct {
Q int64
W *int64
E N
R int64
A []byte
B []string
}
type B struct {
Q *int64
W int64
E string
A *N
B []byte
}
func main() {
w := int64(2)
a := A{1, &w, N{"123", false}, 4, []byte(`{"Ha": "233", "Ok": true}`), []string{"ok", "ha", "cha"}}
b := B{}
err := MigrateFields(&b, &a)
fmt.Println(err)
o, _ := json.Marshal(b)
fmt.Println(string(o))
}
func MigrateFields(dst, src interface{}) error {
srcValue := reflect.ValueOf(src)
dstValue := reflect.ValueOf(dst)
if dstValue.Kind() != reflect.Ptr {
return errors.New("only pointer dst is accepted")
}
if srcValue.IsZero() || dstValue.IsZero() {
return errors.New("nil dst or src")
}
if srcValue.Kind() == reflect.Ptr {
srcValue = srcValue.Elem()
}
dstValue = dstValue.Elem()
if srcValue.Kind() != reflect.Struct || dstValue.Kind() != reflect.Struct {
return errors.New("only structs can be migrated")
}
return copyStruct(dstValue, srcValue)
}
func copyStruct(dst, src reflect.Value) error {
srcType := src.Type()
dstType := dst.Type()
for i := 0; i < srcType.NumField(); i += 1 {
srcField := srcType.Field(i)
srcFieldType := srcField.Type
srcFieldValue := src.FieldByIndex(srcField.Index)
switch srcFieldType.Kind() {
case reflect.Chan, reflect.Interface, reflect.Func:
return unsupportedTypeErr(srcFieldType)
}
dstField, ok := dstType.FieldByName(srcField.Name)
if !ok {
continue
}
dstFieldType := dstField.Type
dstFieldValue := dst.FieldByIndex(dstField.Index)
if !srcFieldValue.IsValid() || srcFieldValue.IsZero() {
continue
}
if srcFieldType.Kind() == reflect.Ptr {
srcFieldValue = srcFieldValue.Elem()
}
if dstFieldType.Kind() == reflect.Ptr {
dstTypeIns := reflect.New(dstFieldType.Elem())
dstFieldValue.Set(dstTypeIns)
dstFieldValue = dstTypeIns.Elem()
}
if err := copyValue(dstFieldValue, srcFieldValue); err != nil {
return err
}
}
return nil
}
func copyValue(dst, src reflect.Value) error {
srcType := src.Type()
dstType := dst.Type()
if srcType == dstType {
dst.Set(src)
return nil
}
switch srcType.Kind() {
case reflect.Struct:
switch dstType.Kind() {
case reflect.Struct:
return copyStruct(dst, src)
case reflect.Slice, reflect.String:
return zip(dst, src)
}
case reflect.Map:
switch dstType.Kind() {
case reflect.Slice, reflect.String:
return zip(dst, src)
}
case reflect.String:
switch dstType.Kind() {
case reflect.Struct, reflect.Map, reflect.Slice:
return unzip(dst, src)
}
case reflect.Slice:
switch dstType.Kind() {
case reflect.String:
return zip(dst, src)
case reflect.Slice:
if dstType.Elem().Kind() == reflect.Uint8 {
return zip(dst, src)
} else if srcType.Elem().Kind() == reflect.Uint8 {
return unzip(dst, src)
} else {
return invalidConversionErr(dstType, srcType)
}
}
}
return nil
}
func zip(dst, src reflect.Value) error {
dstKind := dst.Type().Kind()
if dstKind == reflect.Slice && dst.Type().Elem().Kind() != reflect.Uint8 {
return invalidConversionErr(dst.Type(), src.Type())
}
encoded, err := json.Marshal(src.Interface())
if err != nil {
return err
}
if dstKind == reflect.String {
dst.SetString(string(encoded))
} else {
dst.SetBytes(encoded)
}
return nil
}
func unzip(dst, src reflect.Value) error {
srcKind := src.Type().Kind()
if srcKind == reflect.Slice && src.Type().Elem().Kind() != reflect.Uint8 {
return invalidConversionErr(dst.Type(), src.Type())
}
dstTypeIns := reflect.New(dst.Type())
var err error
if srcKind == reflect.String {
err = json.Unmarshal([]byte(src.Interface().(string)), dstTypeIns.Interface())
} else {
err = json.Unmarshal(src.Interface().([]byte), dstTypeIns.Interface())
}
dst.Set(dstTypeIns.Elem())
return err
}
func invalidConversionErr(dstType, srcType reflect.Type) error {
return fmt.Errorf("bad migration: from %s to %s", srcType.Name(), dstType.Name())
}
func unsupportedTypeErr(t reflect.Type) error {
return fmt.Errorf("bad migration: unsupported type %s", t.Name())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment