Created
April 13, 2020 15:14
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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