Files
yandex-music-api-go/internal/models/base.go
T
Mrixs dbb823ade3 Fix R128 and remaining ID fields to accept both JSON types
- Track.R128 and TrackShort.R128: float64 → JSONR128 (accepts both JSON number and object)
- All remaining primary ID fields: string → JSONID (accepts both JSON string and number)
  Fixed in: account, artist, clip, concert, feed, label, landing, metatag,
  music_history, pin, queue, radio, search, settings, supplement
2026-10-18 11:01:26 +03:00

144 lines
4.3 KiB
Go

// 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)
}
// JSONR128 is a flexible R128 loudness normalization value that accepts both JSON numbers and objects.
// The Yandex Music API sometimes returns r128 as a number (e.g., -23.5) and sometimes
// as an object (e.g., {"value": -23.5}). JSONR128 accepts both and stores as float64.
type JSONR128 float64
// UnmarshalJSON implements json.Unmarshaler, accepting both JSON numbers and objects.
func (r *JSONR128) UnmarshalJSON(data []byte) error {
// Try float64 first
var f float64
if err := json.Unmarshal(data, &f); err == nil {
*r = JSONR128(f)
return nil
}
// Try object with "value" key
var obj struct {
Value float64 `json:"value"`
}
if err := json.Unmarshal(data, &obj); err == nil {
*r = JSONR128(obj.Value)
return nil
}
// Default to 0 if neither format works
*r = 0
return nil
}
// Float64 returns the R128 value as a float64.
func (r JSONR128) Float64() float64 {
return float64(r)
}