github.com/anthropics/anthropic-sdk-go@v1.70.1/internal/apijson/port.go (about) 1 package apijson 2 3 import ( 4 "fmt" 5 "reflect" 6 ) 7 8 // Port copies over values from one struct to another struct. 9 func 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 }