// Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT. package utils import ( "bytes" "encoding/json" "errors" "fmt" "math/big" "reflect" "strconv" "strings" "time" "unsafe" "github.com/LukeHagar/plexgo/types" ) func MarshalJSON(v interface{}, tag reflect.StructTag, topLevel bool) ([]byte, error) { typ, val := dereferencePointers(reflect.TypeOf(v), reflect.ValueOf(v)) switch { case isModelType(typ): if topLevel { return json.Marshal(v) } if isNil(typ, val) { return []byte("null"), nil } out := map[string]json.RawMessage{} for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) fieldVal := val.Field(i) fieldName := field.Name omitEmpty := false omitZero := false jsonTag := field.Tag.Get("json") if jsonTag != "" { for _, tag := range strings.Split(jsonTag, ",") { if tag == "omitempty" { omitEmpty = true } else if tag == "omitzero" { omitZero = true } else { fieldName = tag } } } if (omitEmpty || omitZero) && field.Tag.Get("const") == "" { // Both omitempty and omitzero skip zero values (including nil) if isNil(field.Type, fieldVal) { continue } if omitZero && fieldVal.IsZero() { continue } } if !field.IsExported() && field.Tag.Get("const") == "" { continue } additionalProperties := field.Tag.Get("additionalProperties") if fieldName == "-" && additionalProperties == "" { continue } if additionalProperties == "true" { if isNil(field.Type, fieldVal) { continue } fieldVal := trueReflectValue(fieldVal) if fieldVal.Type().Kind() != reflect.Map { return nil, fmt.Errorf("additionalProperties must be a map") } for _, key := range fieldVal.MapKeys() { r, err := marshalValue(fieldVal.MapIndex(key).Interface(), field.Tag) if err != nil { return nil, err } out[key.String()] = r } continue } var fv interface{} if field.IsExported() { fv = fieldVal.Interface() } else { pt := reflect.New(typ).Elem() pt.Set(val) pf := pt.Field(i) fv = reflect.NewAt(pf.Type(), unsafe.Pointer(pf.UnsafeAddr())).Elem().Interface() } r, err := marshalValue(fv, field.Tag) if err != nil { return nil, err } out[fieldName] = r } return json.Marshal(out) default: return marshalValue(v, tag) } } func UnmarshalJSON(b []byte, v interface{}, tag reflect.StructTag, topLevel bool, requiredFields []string) error { if reflect.TypeOf(v).Kind() != reflect.Ptr { return errors.New("v must be a pointer") } typ, val := dereferencePointers(reflect.TypeOf(v), reflect.ValueOf(v)) switch { case isModelType(typ): if topLevel || bytes.Equal(b, []byte("null")) { return json.Unmarshal(b, v) } var unmarshaled map[string]json.RawMessage if err := json.Unmarshal(b, &unmarshaled); err != nil { return err } missingFields := []string{} for _, requiredField := range requiredFields { if _, ok := unmarshaled[requiredField]; !ok { missingFields = append(missingFields, requiredField) } } if len(missingFields) > 0 { return fmt.Errorf("missing required fields: %s", strings.Join(missingFields, ", ")) } var additionalPropertiesField *reflect.StructField var additionalPropertiesValue *reflect.Value for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) fieldVal := val.Field(i) fieldName := field.Name jsonTag := field.Tag.Get("json") if jsonTag != "" { for _, tag := range strings.Split(jsonTag, ",") { if tag != "omitempty" && tag != "omitzero" { fieldName = tag } } } if field.Tag.Get("additionalProperties") == "true" { additionalPropertiesField = &field additionalPropertiesValue = &fieldVal continue } // If we receive a value for a const field ignore it but mark it as unmarshaled if field.Tag.Get("const") != "" { if r, ok := unmarshaled[fieldName]; ok { val := string(r) if strings.HasPrefix(val, `"`) && strings.HasSuffix(val, `"`) { var err error val, err = strconv.Unquote(val) if err != nil { return fmt.Errorf("failed to unquote const field `%s` value `%s`: %w", fieldName, val, err) } } constValue := field.Tag.Get("const") if val != constValue { return fmt.Errorf("const field `%s` does not match expected value `%s` got `%s`", fieldName, constValue, val) } delete(unmarshaled, fieldName) } } else if !field.IsExported() { continue } value, ok := unmarshaled[fieldName] if !ok { defaultTag, defaultOk := field.Tag.Lookup("default") if defaultOk { value = handleDefaultConstValue(defaultTag, fieldVal.Interface(), field.Tag) ok = true } } else { delete(unmarshaled, fieldName) } if ok { if err := unmarshalValue(value, fieldVal, field.Tag); err != nil { return err } } } keys := make([]string, 0, len(unmarshaled)) for k := range unmarshaled { keys = append(keys, k) } if len(keys) > 0 { if additionalPropertiesField != nil && additionalPropertiesValue != nil { typeOfMap := additionalPropertiesField.Type if additionalPropertiesValue.Type().Kind() == reflect.Interface { typeOfMap = reflect.TypeOf(map[string]interface{}{}) } else if additionalPropertiesValue.Type().Kind() != reflect.Map { return fmt.Errorf("additionalProperties must be a map") } mapValue := reflect.MakeMap(typeOfMap) for key, value := range unmarshaled { val := reflect.New(typeOfMap.Elem()) if err := unmarshalValue(value, val, additionalPropertiesField.Tag); err != nil { return err } if val.Elem().Type().String() == typeOfMap.Elem().String() { mapValue.SetMapIndex(reflect.ValueOf(key), val.Elem()) } else { mapValue.SetMapIndex(reflect.ValueOf(key), trueReflectValue(val)) } } if additionalPropertiesValue.Type().Kind() == reflect.Interface { additionalPropertiesValue.Set(mapValue) } else { additionalPropertiesValue.Set(mapValue) } } } default: return unmarshalValue(b, reflect.ValueOf(v), tag) } return nil } func marshalValue(v interface{}, tag reflect.StructTag) (json.RawMessage, error) { constTag := tag.Get("const") if constTag != "" { return handleDefaultConstValue(constTag, v, tag), nil } if isNil(reflect.TypeOf(v), reflect.ValueOf(v)) { defaultTag, ok := tag.Lookup("default") if ok { return handleDefaultConstValue(defaultTag, v, tag), nil } return []byte("null"), nil } typ, val := dereferencePointers(reflect.TypeOf(v), reflect.ValueOf(v)) switch typ.Kind() { case reflect.Int64: format := tag.Get("integer") if format == "string" { b := val.Interface().(int64) return []byte(fmt.Sprintf(`"%d"`, b)), nil } case reflect.Float64: format := tag.Get("number") if format == "string" { b := val.Interface().(float64) return []byte(fmt.Sprintf(`"%g"`, b)), nil } case reflect.Map: if isNil(typ, val) { return []byte("null"), nil } // Check if the map implements json.Marshaler (like optionalnullable.OptionalNullable[T]) if marshaler, ok := val.Interface().(json.Marshaler); ok { return marshaler.MarshalJSON() } out := map[string]json.RawMessage{} for _, key := range val.MapKeys() { itemVal := val.MapIndex(key) if isNil(itemVal.Type(), itemVal) { out[key.String()] = []byte("null") continue } r, err := marshalValue(itemVal.Interface(), tag) if err != nil { return nil, err } out[key.String()] = r } return json.Marshal(out) case reflect.Slice, reflect.Array: if isNil(typ, val) { return []byte("null"), nil } out := []json.RawMessage{} for i := 0; i < val.Len(); i++ { itemVal := val.Index(i) if isNil(itemVal.Type(), itemVal) { out = append(out, []byte("null")) continue } r, err := marshalValue(itemVal.Interface(), tag) if err != nil { return nil, err } out = append(out, r) } return json.Marshal(out) case reflect.Struct: switch typ { case reflect.TypeOf(time.Time{}): return []byte(fmt.Sprintf(`"%s"`, val.Interface().(time.Time).Format(time.RFC3339Nano))), nil case reflect.TypeOf(big.Int{}): format := tag.Get("bigint") if format == "string" { b := val.Interface().(big.Int) return []byte(fmt.Sprintf(`"%s"`, (&b).String())), nil } } } return json.Marshal(v) } func handleDefaultConstValue(tagValue string, val interface{}, tag reflect.StructTag) json.RawMessage { if tagValue == "null" { return []byte("null") } typ := dereferenceTypePointer(reflect.TypeOf(val)) switch typ { case reflect.TypeOf(time.Time{}): return []byte(fmt.Sprintf(`"%s"`, tagValue)) case reflect.TypeOf(big.Int{}): bigIntTag := tag.Get("bigint") if bigIntTag == "string" { return []byte(fmt.Sprintf(`"%s"`, tagValue)) } case reflect.TypeOf(int64(0)): format := tag.Get("integer") if format == "string" { return []byte(fmt.Sprintf(`"%s"`, tagValue)) } case reflect.TypeOf(float64(0)): format := tag.Get("number") if format == "string" { return []byte(fmt.Sprintf(`"%s"`, tagValue)) } case reflect.TypeOf(types.Date{}): return []byte(fmt.Sprintf(`"%s"`, tagValue)) default: if typ.Kind() == reflect.String { return []byte(fmt.Sprintf("%q", tagValue)) } } return []byte(tagValue) } func unmarshalValue(value json.RawMessage, v reflect.Value, tag reflect.StructTag) error { if bytes.Equal(value, []byte("null")) { if v.CanAddr() { return json.Unmarshal(value, v.Addr().Interface()) } else { return json.Unmarshal(value, v.Interface()) } } typ := dereferenceTypePointer(v.Type()) switch typ.Kind() { case reflect.Int64: var b int64 format := tag.Get("integer") if format == "string" { var s string if err := json.Unmarshal(value, &s); err != nil { return err } var err error b, err = strconv.ParseInt(s, 10, 64) if err != nil { return fmt.Errorf("failed to parse string as int64: %w", err) } if v.Kind() == reflect.Ptr { if v.IsNil() { v.Set(reflect.New(typ)) } v = v.Elem() } v.Set(reflect.ValueOf(b)) return nil } case reflect.Float64: var b float64 format := tag.Get("number") if format == "string" { var s string if err := json.Unmarshal(value, &s); err != nil { return err } var err error b, err = strconv.ParseFloat(s, 64) if err != nil { return fmt.Errorf("failed to parse string as float64: %w", err) } if v.Kind() == reflect.Ptr { if v.IsNil() { v.Set(reflect.New(typ)) } v = v.Elem() } v.Set(reflect.ValueOf(b)) return nil } case reflect.Map: if bytes.Equal(value, []byte("null")) || !isComplexValueType(dereferenceTypePointer(typ.Elem())) { if v.CanAddr() { return json.Unmarshal(value, v.Addr().Interface()) } else { return json.Unmarshal(value, v.Interface()) } } var unmarshaled map[string]json.RawMessage if err := json.Unmarshal(value, &unmarshaled); err != nil { return err } m := reflect.MakeMap(typ) for k, value := range unmarshaled { itemVal := reflect.New(typ.Elem()) if err := unmarshalValue(value, itemVal, tag); err != nil { return err } m.SetMapIndex(reflect.ValueOf(k), itemVal.Elem()) } v.Set(m) return nil case reflect.Slice, reflect.Array: var unmarshaled []json.RawMessage if err := json.Unmarshal(value, &unmarshaled); err != nil { return err } arrVal := reflect.MakeSlice(typ, len(unmarshaled), len(unmarshaled)) for index, value := range unmarshaled { itemVal := reflect.New(typ.Elem()) if err := unmarshalValue(value, itemVal, tag); err != nil { return err } arrVal.Index(index).Set(itemVal.Elem()) } if v.Kind() == reflect.Pointer { if v.IsNil() { v.Set(reflect.New(typ)) } v = v.Elem() } v.Set(arrVal) return nil case reflect.Struct: switch typ { case reflect.TypeOf(time.Time{}): var s string if err := json.Unmarshal(value, &s); err != nil { return err } t, err := time.Parse(time.RFC3339Nano, s) if err != nil { return fmt.Errorf("failed to parse string as time.Time: %w", err) } if v.Kind() == reflect.Ptr { if v.IsNil() { v.Set(reflect.New(typ)) } v = v.Elem() } v.Set(reflect.ValueOf(t)) return nil case reflect.TypeOf(big.Int{}): var b *big.Int format := tag.Get("bigint") if format == "string" { var s string if err := json.Unmarshal(value, &s); err != nil { return err } var ok bool b, ok = new(big.Int).SetString(s, 10) if !ok { return fmt.Errorf("failed to parse string as big.Int") } } else { if err := json.Unmarshal(value, &b); err != nil { return err } } if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Ptr { v = v.Elem() } v.Set(reflect.ValueOf(b)) return nil case reflect.TypeOf(types.Date{}): var s string if err := json.Unmarshal(value, &s); err != nil { return err } d, err := types.DateFromString(s) if err != nil { return fmt.Errorf("failed to parse string as types.Date: %w", err) } if v.Kind() == reflect.Ptr { if v.IsNil() { v.Set(reflect.New(typ)) } v = v.Elem() } v.Set(reflect.ValueOf(d)) return nil } } var val interface{} if v.CanAddr() { val = v.Addr().Interface() } else { val = v.Interface() } return json.Unmarshal(value, val) } func dereferencePointers(typ reflect.Type, val reflect.Value) (reflect.Type, reflect.Value) { if typ.Kind() == reflect.Ptr { typ = typ.Elem() val = val.Elem() } else { return typ, val } return dereferencePointers(typ, val) } func dereferenceTypePointer(typ reflect.Type) reflect.Type { if typ.Kind() == reflect.Ptr { typ = typ.Elem() } else { return typ } return dereferenceTypePointer(typ) } func isComplexValueType(typ reflect.Type) bool { switch typ.Kind() { case reflect.Struct: switch typ { case reflect.TypeOf(time.Time{}): fallthrough case reflect.TypeOf(big.Int{}): fallthrough case reflect.TypeOf(types.Date{}): return true } } return false } func isModelType(typ reflect.Type) bool { if isComplexValueType(typ) { return false } if typ.Kind() == reflect.Struct { return true } return false } // CalculateJSONSize returns the byte size of the JSON representation of a value. // This is used to determine which union type variant has the most data. func CalculateJSONSize(v interface{}) int { data, err := json.Marshal(v) if err != nil { return 0 } return len(data) } // UnionCandidate represents a candidate type during union deserialization type UnionCandidate struct { FieldCount int InexactCount int // Count of fields with unknown/unrecognized enum values Size int Type any // The union type enum value Value any // The unmarshaled value } // FieldCounts holds the result of counting fields in a value type FieldCounts struct { Total int // Total number of populated fields Inexact int // Number of fields with unknown/unrecognized open enum values } // CountFieldsWithInexact recursively counts fields and tracks inexact matches (unknown open enum values). func CountFieldsWithInexact(v interface{}) FieldCounts { if v == nil { return FieldCounts{} } typ := reflect.TypeOf(v) val := reflect.ValueOf(v) // Dereference pointers for typ.Kind() == reflect.Ptr { if val.IsNil() { return FieldCounts{} } typ = typ.Elem() val = val.Elem() } return countFieldsRecursive(typ, val) } // PickBestCandidate selects the best union type candidate using a multi-stage filtering approach: // 1. If multiple candidates, filter by field count (keep only those with max field count) // 2. If still multiple, filter by inexact count (keep only those with min inexact count) // 3. If still multiple, filter by JSON size (keep only those with max size) // 4. Return the first remaining candidate func PickBestCandidate(candidates []UnionCandidate) *UnionCandidate { if len(candidates) == 0 { return nil } if len(candidates) == 1 { return &candidates[0] } // Filter by field count if we have multiple candidates if len(candidates) > 1 { maxFieldCount := -1 for i := range candidates { fieldCounts := CountFieldsWithInexact(candidates[i].Value) candidates[i].FieldCount = fieldCounts.Total candidates[i].InexactCount = fieldCounts.Inexact if candidates[i].FieldCount > maxFieldCount { maxFieldCount = candidates[i].FieldCount } } // Keep only candidates with maximum field count filtered := make([]UnionCandidate, 0, len(candidates)) for _, c := range candidates { if c.FieldCount == maxFieldCount { filtered = append(filtered, c) } } candidates = filtered } if len(candidates) == 1 { return &candidates[0] } // Filter by inexact count if we still have multiple candidates // Prefer candidates with fewer unknown/unrecognized enum values if len(candidates) > 1 { minInexactCount := int(^uint(0) >> 1) // max int for _, c := range candidates { if c.InexactCount < minInexactCount { minInexactCount = c.InexactCount } } // Keep only candidates with minimum inexact count filtered := make([]UnionCandidate, 0, len(candidates)) for _, c := range candidates { if c.InexactCount == minInexactCount { filtered = append(filtered, c) } } candidates = filtered } if len(candidates) == 1 { return &candidates[0] } // Filter by JSON size if we still have multiple candidates if len(candidates) > 1 { maxSize := -1 for i := range candidates { candidates[i].Size = CalculateJSONSize(candidates[i].Value) if candidates[i].Size > maxSize { maxSize = candidates[i].Size } } // Keep only candidates with maximum size filtered := make([]UnionCandidate, 0, len(candidates)) for _, c := range candidates { if c.Size == maxSize { filtered = append(filtered, c) } } candidates = filtered } // Pick the first remaining candidate return &candidates[0] } func countFieldsRecursive(typ reflect.Type, val reflect.Value) FieldCounts { counts := FieldCounts{} // Check if the value has an IsExact() method (for open enums) // Try both the value and its pointer if val.CanInterface() && val.CanAddr() { ptrVal := val.Addr() if method := ptrVal.MethodByName("IsExact"); method.IsValid() { results := method.Call(nil) if len(results) == 1 && results[0].Kind() == reflect.Bool { isExact := results[0].Bool() counts.Total = 1 if !isExact { counts.Inexact = 1 // Unknown enum value } return counts } } } switch typ.Kind() { case reflect.Struct: // Handle special types switch typ { case reflect.TypeOf(time.Time{}): if !val.Interface().(time.Time).IsZero() { return FieldCounts{Total: 1} } return FieldCounts{} case reflect.TypeOf(big.Int{}): b := val.Interface().(big.Int) if b.Sign() != 0 { return FieldCounts{Total: 1} } return FieldCounts{} case reflect.TypeOf(types.Date{}): // Date is always counted if it exists return FieldCounts{Total: 1} } // For regular structs, count non-zero fields for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) fieldVal := val.Field(i) // Skip unexported fields and const fields if !field.IsExported() || field.Tag.Get("const") != "" { continue } // Skip fields tagged with json:"-" jsonTag := field.Tag.Get("json") if jsonTag == "-" { continue } fieldTyp := field.Type // Dereference pointer types for the field for fieldTyp.Kind() == reflect.Ptr { if fieldVal.IsNil() { break } fieldTyp = fieldTyp.Elem() fieldVal = fieldVal.Elem() } if !isNil(field.Type, val.Field(i)) { fieldCounts := countFieldsRecursive(fieldTyp, fieldVal) counts.Total += fieldCounts.Total counts.Inexact += fieldCounts.Inexact } } case reflect.Slice, reflect.Array: if val.IsNil() || val.Len() == 0 { return FieldCounts{} } // Count each array/slice element for i := 0; i < val.Len(); i++ { itemVal := val.Index(i) itemTyp := itemVal.Type() // Dereference pointer types for itemTyp.Kind() == reflect.Ptr { if itemVal.IsNil() { break } itemTyp = itemTyp.Elem() itemVal = itemVal.Elem() } if !isNil(itemTyp, itemVal) { itemCounts := countFieldsRecursive(itemTyp, itemVal) counts.Total += itemCounts.Total counts.Inexact += itemCounts.Inexact } } case reflect.String: if val.String() != "" { counts.Total = 1 } case reflect.Bool: // Bools always count as a field (even if false) counts.Total = 1 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if val.Int() != 0 { counts.Total = 1 } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if val.Uint() != 0 { counts.Total = 1 } case reflect.Float32, reflect.Float64: if val.Float() != 0 { counts.Total = 1 } default: // For any other type, if it's not zero, count it as 1 if !val.IsZero() { counts.Total = 1 } } return counts }