openai/openai-go
Publicmirrored from https://github.com/openai/openai-goAvailable
packages/param/encoder.go
125lines · modecode
| 1 | package param |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "fmt" |
| 6 | "reflect" |
| 7 | "strings" |
| 8 | "sync" |
| 9 | "time" |
| 10 | |
| 11 | shimjson "github.com/openai/openai-go/internal/encoding/json" |
| 12 | |
| 13 | "github.com/tidwall/sjson" |
| 14 | ) |
| 15 | |
| 16 | // This type will not be stable and shouldn't be relied upon |
| 17 | type EncodedAsDate Opt[time.Time] |
| 18 | |
| 19 | func (m EncodedAsDate) MarshalJSON() ([]byte, error) { |
| 20 | underlying := Opt[time.Time](m) |
| 21 | bytes := underlying.MarshalJSONWithTimeLayout("2006-01-02") |
| 22 | if len(bytes) > 0 { |
| 23 | return bytes, nil |
| 24 | } |
| 25 | return underlying.MarshalJSON() |
| 26 | } |
| 27 | |
| 28 | // This uses a shimmed 'encoding/json' from Go 1.24, to support the 'omitzero' tag |
| 29 | func MarshalObject[T OverridableObject](f T, underlying any) ([]byte, error) { |
| 30 | if f.IsNull() { |
| 31 | return []byte("null"), nil |
| 32 | } else if extras := f.GetExtraFields(); extras != nil { |
| 33 | bytes, err := shimjson.Marshal(underlying) |
| 34 | if err != nil { |
| 35 | return nil, err |
| 36 | } |
| 37 | for k, v := range extras { |
| 38 | bytes, err = sjson.SetBytes(bytes, k, v) |
| 39 | if err != nil { |
| 40 | return nil, err |
| 41 | } |
| 42 | } |
| 43 | return bytes, nil |
| 44 | } else if ovr, ok := f.IsOverridden(); ok { |
| 45 | return shimjson.Marshal(ovr) |
| 46 | } else { |
| 47 | return shimjson.Marshal(underlying) |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | // This uses a shimmed 'encoding/json' from Go 1.24, to support the 'omitzero' tag |
| 52 | func MarshalUnion[T any](variants ...any) ([]byte, error) { |
| 53 | nPresent := 0 |
| 54 | presentIdx := -1 |
| 55 | for i, variant := range variants { |
| 56 | if !IsOmitted(variant) { |
| 57 | nPresent++ |
| 58 | presentIdx = i |
| 59 | } |
| 60 | } |
| 61 | if nPresent == 0 || presentIdx == -1 { |
| 62 | return []byte(`null`), nil |
| 63 | } else if nPresent > 1 { |
| 64 | return nil, &json.MarshalerError{ |
| 65 | Type: typeFor[T](), |
| 66 | Err: fmt.Errorf("expected union to have only one present variant, got %d", nPresent), |
| 67 | } |
| 68 | } |
| 69 | return shimjson.Marshal(variants[presentIdx]) |
| 70 | } |
| 71 | |
| 72 | // shimmed from Go 1.23 "reflect" package |
| 73 | func typeFor[T any]() reflect.Type { |
| 74 | var v T |
| 75 | if t := reflect.TypeOf(v); t != nil { |
| 76 | return t // optimize for T being a non-interface kind |
| 77 | } |
| 78 | return reflect.TypeOf((*T)(nil)).Elem() // only for an interface kind |
| 79 | } |
| 80 | |
| 81 | var optStringType = typeFor[Opt[string]]() |
| 82 | var optIntType = typeFor[Opt[int64]]() |
| 83 | var optFloatType = typeFor[Opt[float64]]() |
| 84 | var optBoolType = typeFor[Opt[bool]]() |
| 85 | |
| 86 | var OptionalPrimitiveTypes map[reflect.Type][]int |
| 87 | |
| 88 | // indexOfUnderlyingValueField must only be called at initialization time |
| 89 | func indexOfUnderlyingValueField(t reflect.Type) []int { |
| 90 | field, ok := t.FieldByName("Value") |
| 91 | if !ok { |
| 92 | panic("unreachable: initialization issue, underlying value field not found") |
| 93 | } |
| 94 | return field.Index |
| 95 | } |
| 96 | |
| 97 | func init() { |
| 98 | OptionalPrimitiveTypes = map[reflect.Type][]int{ |
| 99 | optStringType: indexOfUnderlyingValueField(optStringType), |
| 100 | optIntType: indexOfUnderlyingValueField(optIntType), |
| 101 | optFloatType: indexOfUnderlyingValueField(optFloatType), |
| 102 | optBoolType: indexOfUnderlyingValueField(optBoolType), |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | var structFieldsCache sync.Map |
| 107 | |
| 108 | func structFields(t reflect.Type) (map[string][]int, error) { |
| 109 | if cached, ok := structFieldsCache.Load(t); ok { |
| 110 | return cached.(map[string][]int), nil |
| 111 | } |
| 112 | if t.Kind() != reflect.Struct { |
| 113 | return nil, fmt.Errorf("resp: expected struct but got %v of kind %v", t.String(), t.Kind().String()) |
| 114 | } |
| 115 | structFields := map[string][]int{} |
| 116 | for i := 0; i < t.NumField(); i++ { |
| 117 | field := t.Field(i) |
| 118 | name := strings.Split(field.Tag.Get("json"), ",")[0] |
| 119 | if name == "" || name == "-" || field.Anonymous { |
| 120 | continue |
| 121 | } |
| 122 | structFields[name] = field.Index |
| 123 | } |
| 124 | return structFields, nil |
| 125 | } |
| 126 | |