github.com/anthropics/anthropic-sdk-go@v1.70.1/packages/param/encoder.go (about) 1 package param 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "reflect" 7 "strings" 8 "time" 9 10 shimjson "github.com/anthropics/anthropic-sdk-go/internal/encoding/json" 11 12 "github.com/tidwall/sjson" 13 ) 14 15 // EncodedAsDate is not be stable and shouldn't be relied upon 16 type EncodedAsDate Opt[time.Time] 17 18 // If we want to set a literal key value into JSON using sjson, we need to make sure it doesn't have 19 // special characters that sjson interprets as a path. 20 var EscapeSJSONKey = strings.NewReplacer("\\", "\\\\", "|", "\\|", "#", "\\#", "@", "\\@", "*", "\\*", ".", "\\.", ":", "\\:", "?", "\\?").Replace 21 22 type forceOmit int 23 24 func (m EncodedAsDate) MarshalJSON() ([]byte, error) { 25 underlying := Opt[time.Time](m) 26 bytes := underlying.MarshalJSONWithTimeLayout("2006-01-02") 27 if len(bytes) > 0 { 28 return bytes, nil 29 } 30 return underlying.MarshalJSON() 31 } 32 33 // MarshalObject uses a shimmed 'encoding/json' from Go 1.24, to support the 'omitzero' tag 34 // 35 // Stability for the API of MarshalObject is not guaranteed. 36 func MarshalObject[T ParamStruct](f T, underlying any) ([]byte, error) { 37 return MarshalWithExtras(f, underlying, f.extraFields()) 38 } 39 40 // MarshalWithExtras is used to marshal a struct with additional properties. 41 // 42 // Stability for the API of MarshalWithExtras is not guaranteed. 43 func MarshalWithExtras[T ParamStruct, R any](f T, underlying any, extras map[string]R) ([]byte, error) { 44 if f.null() { 45 return []byte("null"), nil 46 } else if len(extras) > 0 { 47 bytes, err := shimjson.Marshal(underlying) 48 if err != nil { 49 return nil, err 50 } 51 for k, v := range extras { 52 var a any = v 53 if a == Omit { 54 // Errors when handling ForceOmitted are ignored. 55 if b, e := sjson.DeleteBytes(bytes, EscapeSJSONKey(k)); e == nil { 56 bytes = b 57 } 58 continue 59 } 60 // Marshal the value ourselves so extras are HTML-escaped and 61 // validated like every other field; sjson would insert it raw. 62 raw, err := shimjson.Marshal(v) 63 if err != nil { 64 return nil, err 65 } 66 bytes, err = sjson.SetRawBytes(bytes, EscapeSJSONKey(k), raw) 67 if err != nil { 68 return nil, err 69 } 70 } 71 return bytes, nil 72 } else if ovr, ok := f.Overrides(); ok { 73 return shimjson.Marshal(ovr) 74 } else { 75 return shimjson.Marshal(underlying, shimjson.WithSkipCompaction(true)) 76 } 77 } 78 79 // MarshalUnion uses a shimmed 'encoding/json' from Go 1.24, to support the 'omitzero' tag 80 // 81 // Stability for the API of MarshalUnion is not guaranteed. 82 func MarshalUnion[T ParamStruct](metadata T, variants ...any) ([]byte, error) { 83 nPresent := 0 84 presentIdx := -1 85 for i, variant := range variants { 86 if !IsOmitted(variant) { 87 nPresent++ 88 presentIdx = i 89 } 90 } 91 if nPresent == 0 || presentIdx == -1 { 92 if metadata.null() { 93 return []byte("null"), nil 94 } 95 if ovr, ok := metadata.Overrides(); ok { 96 return shimjson.Marshal(ovr) 97 } 98 return []byte(`null`), nil 99 } else if nPresent > 1 { 100 return nil, &json.MarshalerError{ 101 Type: typeFor[T](), 102 Err: fmt.Errorf("expected union to have only one present variant, got %d", nPresent), 103 } 104 } 105 return shimjson.Marshal(variants[presentIdx], shimjson.WithSkipCompaction(true)) 106 } 107 108 // typeFor is shimmed from Go 1.23 "reflect" package 109 func typeFor[T any]() reflect.Type { 110 var v T 111 if t := reflect.TypeOf(v); t != nil { 112 return t // optimize for T being a non-interface kind 113 } 114 return reflect.TypeOf((*T)(nil)).Elem() // only for an interface kind 115 }