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  }