#721·psst

Fix: Search crashes due to recent Spotify API changes (null items, missing tracks/publisher)

Author: juanmalopmanCreated Mar 2, 2026Updated Mar 2, 2026
Labelsbug

(Note: I am running this on top of PR #720 (https://github.com/jpochyla/psst/pull/720) since the master branch currently cannot get past the login screen. Once logged in via that PR's WebAPI fix, I ran into these search crashes.)

Describe the bug When searching any song/artist/album: Error: json: invalid type: null, expected struct Playlist at line 1 column 16998 ...and after fixing it: Error: json: missing field 'tracks' at line 1 column 18887

To Reproduce Search for any song.

Expected behavior Return a list of results.

Environment

  • OS: Win 11

Additional context

I guess Spotify changed their API payloads, causing psst to crash immediately when using the Search function.

I traced the panics to three specific API payload changes and successfully fixed them locally:

1. Search arrays occasionally return null items The items array sometimes contains null instead of a valid struct (e.g., if a track is region-blocked). Fix: In psst-gui/src/data/utils.rs, I updated the Page<T> struct to safely drop nulls using a custom deserializer:

#[derive(Deserialize, Default)]
pub struct Page<T: Clone> {
    #[serde(
        deserialize_with = "deserialize_ignore_nulls",
        bound = "T: serde::Deserialize<'de>"
    )]
    pub items: Vector<T>,
    
    pub limit: usize,
    pub offset: usize,
    pub total: usize,
}

And added this helper:

pub fn deserialize_ignore_nulls<'de, D, T>(deserializer: D) -> Result<Vector<T>, D::Error>
where
    D: serde::Deserializer<'de>,
    T: serde::Deserialize<'de> + Clone,
{
    let opt_vec: Option<Vec<Option<T>>> = Option::deserialize(deserializer)?;
    Ok(opt_vec.unwrap_or_default().into_iter().flatten().collect())
}
  1. Playlists occasionally omit the tracks field entirely Fix: In psst-gui/src/data/playlist.rs, added #[serde(default)] to pub track_count: Option and made the deserialize_track_count helper gracefully handle nulls via Option::deserialize.
#[derive(Clone, Debug, Data, Lens, Deserialize)]
pub struct Playlist {
    pub id: Arc<str>,
    pub name: Arc<str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub images: Option<Vector<Image>>,
    #[serde(deserialize_with = "deserialize_description")]
    pub description: Arc<str>,
    #[serde(default)]
    #[serde(rename = "tracks")]
    #[serde(deserialize_with = "deserialize_track_count")]
    pub track_count: Option<usize>,
    pub owner: PublicUser,
    pub collaborative: bool,
    #[serde(rename = "public")]
    pub public: Option<bool>,
}
  1. Shows/Podcasts occasionally omit the publisher field Fix: In psst-gui/src/data/show.rs, added #[serde(default)] above pub publisher: Arc.
#[derive(Clone, Data, Lens, Deserialize)]
pub struct Show {
    pub id: Arc<str>,
    pub name: Arc<str>,
    pub images: Vector<Image>,
    #[serde(default)]
    pub publisher: Arc<str>,
    pub description: Arc<str>,
    pub total_episodes: Option<usize>,
}

These three simple serde additions completely restored Search functionality for me! Can't load an Artist page but can use the app for the most part.