fix: unmarshal LB top recordings from snake_case wire format, cap at 10

The ListenBrainz popularity API returns snake_case JSON fields
(recording_name, artist_name, total_listen_count, recording_mbid)
but LBTopRecording used camelCase JSON tags for Wails serialization.
All fields silently deserialized as zero values — empty strings and
zero counts — producing ~8000 blank rows in the top tracks section.

Fix: add lbTopRecordingWire with snake_case tags for API unmarshal,
convert to LBTopRecording (camelCase) for Wails. Cap results at 10
to avoid rendering thousands of rows for prolific artists.
This commit is contained in:
2026-03-24 19:35:34 -04:00
parent a62b1da474
commit eabcf8395e
2 changed files with 39 additions and 3 deletions
+16 -3
View File
@@ -68,12 +68,25 @@ func (c *ListenBrainzClient) TopRecordingsForArtist(
return nil, fmt.Errorf("listenbrainz top recordings: %w", err)
}
// The API returns an array directly.
var out []LBTopRecording
if err := json.Unmarshal(body, &out); err != nil {
// The API returns snake_case JSON — unmarshal into wire type,
// then convert to the camelCase Wails type.
var wire []lbTopRecordingWire
if err := json.Unmarshal(body, &wire); err != nil {
return nil, fmt.Errorf("listenbrainz top recordings unmarshal: %w", err)
}
const maxTopRecordings = 10
limit := len(wire)
if limit > maxTopRecordings {
limit = maxTopRecordings
}
out := make([]LBTopRecording, limit)
for i := range limit {
out[i] = wire[i].toPublic()
}
c.cacheJSON(cacheKey, out, cacheTTLSearch, artistMBID, "artist")
return out, nil
+23
View File
@@ -68,6 +68,10 @@ type MBTrack struct {
// LBTopRecording represents a popular recording from the
// ListenBrainz popularity API.
//
// JSON tags use camelCase for Wails→frontend serialization.
// The API response uses snake_case, so we unmarshal into
// lbTopRecordingWire first, then convert.
type LBTopRecording struct {
RecordingMBID string `json:"recordingMbid"`
ArtistName string `json:"artistName"`
@@ -75,6 +79,25 @@ type LBTopRecording struct {
TotalListenCount int `json:"totalListenCount"`
}
// lbTopRecordingWire matches the ListenBrainz API's snake_case
// JSON response for the popularity/top-recordings-for-artist
// endpoint.
type lbTopRecordingWire struct {
RecordingMBID string `json:"recording_mbid"`
ArtistName string `json:"artist_name"`
RecordingName string `json:"recording_name"`
TotalListenCount int `json:"total_listen_count"`
}
func (w lbTopRecordingWire) toPublic() LBTopRecording {
return LBTopRecording{
RecordingMBID: w.RecordingMBID,
ArtistName: w.ArtistName,
TrackName: w.RecordingName,
TotalListenCount: w.TotalListenCount,
}
}
// LBSimilarArtist represents a similar artist from the
// ListenBrainz labs API.
type LBSimilarArtist struct {