1package apijson
2
3import (
4 "fmt"
5 "reflect"
6)
7
8// Port copies over values from one struct to another struct.
9func Port(from any, to any) error {
10 toVal := reflect.ValueOf(to)
11 fromVal := reflect.ValueOf(from)
12
13 if toVal.Kind() != reflect.Ptr || toVal.IsNil() {
14 return fmt.Errorf("destination must be a non-nil pointer")
15 }
16
17 for toVal.Kind() == reflect.Ptr {
18 toVal = toVal.Elem()
19 }
20 toType := toVal.Type()
21
22 for fromVal.Kind() == reflect.Ptr {
23 fromVal = fromVal.Elem()
24 }
25 fromType := fromVal.Type()
26
27 if toType.Kind() != reflect.Struct {
28 return fmt.Errorf("destination must be a non-nil pointer to a struct (%v %v)", toType, toType.Kind())
29 }
30
31 values := map[string]reflect.Value{}
32 fields := map[string]reflect.Value{}
33
34 fromJSON := fromVal.FieldByName("JSON")
35 toJSON := toVal.FieldByName("JSON")
36
37 // Iterate through the fields of v and load all the "normal" fields in the struct to the map of
38 // string to reflect.Value, as well as their raw .JSON.Foo counterpart indicated by j.
39 var getFields func(t reflect.Type, v reflect.Value)
40 getFields = func(t reflect.Type, v reflect.Value) {
41 j := v.FieldByName("JSON")
42
43 // Recurse into anonymous fields first, since the fields on the object should win over the fields in the
44 // embedded object.
45 for i := 0; i < t.NumField(); i++ {
46 field := t.Field(i)
47 if field.Anonymous {
48 getFields(field.Type, v.Field(i))
49 continue
50 }
51 }
52
53 for i := 0; i < t.NumField(); i++ {
54 field := t.Field(i)
55 ptag, ok := parseJSONStructTag(field)
56 if !ok || ptag.name == "-" || ptag.name == "" {
57 continue
58 }
59 values[ptag.name] = v.Field(i)
60 if j.IsValid() {
61 fields[ptag.name] = j.FieldByName(field.Name)
62 }
63 }
64 }
65 getFields(fromType, fromVal)
66
67 // Use the values from the previous step to populate the 'to' struct.
68 for i := 0; i < toType.NumField(); i++ {
69 field := toType.Field(i)
70 ptag, ok := parseJSONStructTag(field)
71 if !ok {
72 continue
73 }
74 if ptag.name == "-" {
75 continue
76 }
77 if value, ok := values[ptag.name]; ok {
78 delete(values, ptag.name)
79 if field.Type.Kind() == reflect.Interface {
80 toVal.Field(i).Set(value)
81 } else {
82 switch value.Kind() {
83 case reflect.String:
84 toVal.Field(i).SetString(value.String())
85 case reflect.Bool:
86 toVal.Field(i).SetBool(value.Bool())
87 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
88 toVal.Field(i).SetInt(value.Int())
89 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
90 toVal.Field(i).SetUint(value.Uint())
91 case reflect.Float32, reflect.Float64:
92 toVal.Field(i).SetFloat(value.Float())
93 default:
94 toVal.Field(i).Set(value)
95 }
96 }
97 }
98
99 if fromJSONField, ok := fields[ptag.name]; ok {
100 if toJSONField := toJSON.FieldByName(field.Name); toJSONField.IsValid() {
101 toJSONField.Set(fromJSONField)
102 }
103 }
104 }
105
106 // Finally, copy over the .JSON.raw and .JSON.ExtraFields
107 if toJSON.IsValid() {
108 if raw := toJSON.FieldByName("raw"); raw.IsValid() {
109 setUnexportedField(raw, fromJSON.Interface().(interface{ RawJSON() string }).RawJSON())
110 }
111
112 if toExtraFields := toJSON.FieldByName("ExtraFields"); toExtraFields.IsValid() {
113 if fromExtraFields := fromJSON.FieldByName("ExtraFields"); fromExtraFields.IsValid() {
114 setUnexportedField(toExtraFields, fromExtraFields.Interface())
115 }
116 }
117 }
118
119 return nil
120}