// Package models provides data models for the Yandex Music API. // All models implement the Model interface with DeJSON/DeList deserialization methods. package models import ( "encoding/json" "fmt" "strconv" "gitea.mrixs.me/Mrixs/yandex-music-api-go/internal/utils" ) // Model is the base interface for all models. // All model types implement this interface for JSON deserialization and serialization. type Model interface { // DeJSON deserializes a model from JSON data. DeJSON(data []byte, c ClientInterface) (Model, error) // DeList deserializes a list of models from JSON data. DeList(data []byte, c ClientInterface) ([]Model, error) // ToJSON serializes the model to JSON. ToJSON() ([]byte, error) // ToMap converts the model to a map. ToMap() (map[string]any, error) } // BaseModel provides common functionality for models, including a client reference. type BaseModel struct { Client ClientInterface `json:"-"` } // NewBaseModel creates a new BaseModel with the given client interface. func NewBaseModel(c ClientInterface) *BaseModel { return &BaseModel{Client: c} } // DeJSON implements Model.DeJSON for basic models. // This should be overridden by concrete types. func (b *BaseModel) DeJSON(data []byte, c ClientInterface) (Model, error) { // This should be overridden by concrete types return nil, nil } // DeList implements Model.DeList for basic models. // This should be overridden by concrete types. func (b *BaseModel) DeList(data []byte, c ClientInterface) ([]Model, error) { var rawList []json.RawMessage if err := json.Unmarshal(data, &rawList); err != nil { return nil, err } result := make([]Model, 0, len(rawList)) for range rawList { // This should be overridden by concrete types result = append(result, nil) } return result, nil } // ToJSON serializes the model to JSON using the default JSON backend. func (b *BaseModel) ToJSON() ([]byte, error) { return utils.Marshal(b) } // ToMap converts the model to a map by serializing to JSON and unmarshaling into a map. func (b *BaseModel) ToMap() (map[string]any, error) { data, err := b.ToJSON() if err != nil { return nil, err } var result map[string]any if err := json.Unmarshal(data, &result); err != nil { return nil, err } return result, nil } // SetClient sets the client interface on the model. func (b *BaseModel) SetClient(c ClientInterface) { b.Client = c } // GetClient returns the client interface associated with the model. func (b *BaseModel) GetClient() ClientInterface { return b.Client } // JSONID is an ID that can be represented as either a JSON string or a JSON number. // The Yandex Music API sometimes returns IDs as numbers (e.g., 12345) and sometimes // as strings (e.g., "12345"). JSONID accepts both and stores the value as a string. type JSONID string // UnmarshalJSON implements json.Unmarshaler, accepting both JSON strings and numbers. func (id *JSONID) UnmarshalJSON(data []byte) error { // Try string first var s string if err := json.Unmarshal(data, &s); err == nil { *id = JSONID(s) return nil } // Try number (float64 is the default JSON number decoding) var f float64 if err := json.Unmarshal(data, &f); err != nil { return fmt.Errorf("cannot unmarshal %v into JSONID: %w", string(data), err) } *id = JSONID(strconv.FormatFloat(f, 'f', -1, 64)) return nil } // String returns the ID as a Go string. func (id JSONID) String() string { return string(id) } // R128 represents EBU R 128 loudness normalization parameters. // The Yandex Music API returns this either as a number (e.g., -23.5) or as an object // with fields i, tp, important_secs. type R128 struct { BaseModel I float64 `json:"i"` Tp float64 `json:"tp"` ImportantSecs int `json:"important_secs"` } // UnmarshalJSON implements json.Unmarshaler, accepting both JSON numbers and objects. func (r *R128) UnmarshalJSON(data []byte) error { // Try float64 first (API sometimes returns just a number) var f float64 if err := json.Unmarshal(data, &f); err == nil { r.I = f r.Tp = 0 r.ImportantSecs = 0 return nil } // Try object with fields i, tp, important_secs var obj struct { I float64 `json:"i"` Tp float64 `json:"tp"` ImportantSecs int `json:"important_secs"` } if err := json.Unmarshal(data, &obj); err != nil { return err } r.I = obj.I r.Tp = obj.Tp r.ImportantSecs = obj.ImportantSecs return nil } // Float64 returns the integrated loudness (i) value as a float64. func (r R128) Float64() float64 { return r.I } // DeJSON deserializes an R128 from JSON data. func (r *R128) DeJSON(data []byte, c ClientInterface) (Model, error) { r128 := &R128{BaseModel: *NewBaseModel(c)} if err := r128.UnmarshalJSON(data); err != nil { return nil, err } return r128, nil } // DeList deserializes a list of R128 from JSON data. func (r *R128) DeList(data []byte, c ClientInterface) ([]Model, error) { var rawList []json.RawMessage if err := json.Unmarshal(data, &rawList); err != nil { return nil, err } result := make([]Model, 0, len(rawList)) for _, item := range rawList { r128 := &R128{BaseModel: *NewBaseModel(c)} if err := r128.UnmarshalJSON(item); err != nil { continue } result = append(result, r128) } return result, nil }