// Package utils provides utility functions for the Yandex Music API client. package utils import ( "encoding/json" "reflect" "strings" ) // JSONBackend defines the interface for JSON encoding/decoding. // Implementations can use encoding/json, json-iterator, or other backends. type JSONBackend interface { // Marshal encodes a Go value to JSON. Marshal(v any) ([]byte, error) // Unmarshal decodes JSON data into a Go value. Unmarshal(data []byte, v any) error } // DefaultJSONBackend uses the standard library encoding/json. type DefaultJSONBackend struct{} // Marshal encodes a Go value to JSON using encoding/json. func (DefaultJSONBackend) Marshal(v any) ([]byte, error) { return json.Marshal(v) } // Unmarshal decodes JSON data into a Go value using encoding/json. func (DefaultJSONBackend) Unmarshal(data []byte, v any) error { return json.Unmarshal(data, v) } // GetDefaultJSONBackend returns the default JSON backend (encoding/json). func GetDefaultJSONBackend() JSONBackend { return DefaultJSONBackend{} } // RawMessage is a raw encoded JSON value. // It is an alias for encoding/json.RawMessage. type RawMessage = json.RawMessage // Marshal is a convenience function that marshals a value using the default JSON backend. func Marshal(v any) ([]byte, error) { return GetDefaultJSONBackend().Marshal(v) } // Unmarshal is a convenience function that unmarshals data using the default JSON backend. func Unmarshal(data []byte, v any) error { return GetDefaultJSONBackend().Unmarshal(data, v) } // ToSnakeCase converts camelCase to snake_case. // For example, "camelCase" becomes "camel_case". func ToSnakeCase(s string) string { if s == "" { return s } var result strings.Builder for i, r := range s { if r >= 'A' && r <= 'Z' { if i > 0 { result.WriteRune('_') } result.WriteRune(r + 'a' - 'A') } else { result.WriteRune(r) } } return result.String() } // ToCamelCase converts snake_case to camelCase. // For example, "snake_case" becomes "snakeCase". func ToCamelCase(s string) string { if s == "" { return s } parts := strings.Split(s, "_") if len(parts) == 1 { return s } var result strings.Builder result.WriteString(parts[0]) for _, part := range parts[1:] { if len(part) > 0 { result.WriteString(strings.ToUpper(part[:1]) + part[1:]) } } return result.String() } // MapKeysToSnakeCase converts all keys in a map to snake_case. func MapKeysToSnakeCase(m map[string]any) map[string]any { result := make(map[string]any, len(m)) for k, v := range m { result[ToSnakeCase(k)] = v } return result } // MapKeysToCamelCase converts all keys in a map to camelCase. func MapKeysToCamelCase(m map[string]any) map[string]any { result := make(map[string]any, len(m)) for k, v := range m { result[ToCamelCase(k)] = v } return result } // StructToMap converts a struct to a map using json tags. // The forRequest parameter is currently unused but reserved for future use. func StructToMap(v any, forRequest bool) (map[string]any, error) { data, err := Marshal(v) if err != nil { return nil, err } var result map[string]any if err := Unmarshal(data, &result); err != nil { return nil, err } return result, nil } // GetJSONTag returns the json tag name for a struct field at the given index. // It returns an empty string if the field has no json tag or the tag is "-". func GetJSONTag(t reflect.Type, fieldIndex int) string { if fieldIndex >= t.NumField() { return "" } field := t.Field(fieldIndex) tag := field.Tag.Get("json") if tag == "" || tag == "-" { return "" } // Parse tag options (e.g., "name,omitempty") parts := strings.Split(tag, ",") return parts[0] }