Skip to content
Prev Previous commit
Next Next commit
test(models): pin the parsers that must not be deleted
ModelObjectGson delegates to the hand-written parser on Disallows, ExternalId
and ExternalUrl, so removing one of those overrides makes the inherited
reflective path call the adapter that calls it back, until the stack runs out.
PlaybackQueue's override is what returns an empty list for a missing "queue".

Disallows, ExternalUrl and PlaybackQueue were already covered by request tests.
ExternalId was not covered at all, and no model object holds one, so it is read
directly here. Each override also carries a note saying why it exists.
  • Loading branch information
dargmuesli committed Sep 1, 2026
commit 58ecfc851692d8e3ad794dd051c5a1a70e98602e
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ public PlaybackQueue build() {
/**
* JsonUtil class for building {@link PlaybackQueue} instances.
*/
// Do not delete this override: it returns an empty list for a missing "queue",
// where the inherited reflective createModelObject would leave the field null.
public static final class JsonUtil extends AbstractModelObject.JsonUtil<PlaybackQueue> {

public JsonUtil() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ public Disallows build() {
/**
* JsonUtil class for building {@link Disallows} instances.
*/
// Do not delete this override: ModelObjectGson registers an adapter for Disallows that
// delegates here, and the inherited reflective createModelObject would call that
// adapter straight back into this class.
public static final class JsonUtil extends AbstractModelObject.JsonUtil<Disallows> {

public JsonUtil() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ public ExternalId build() {
* JsonUtil class for building {@link ExternalId} instances.
*/
@SuppressWarnings("unchecked")
// Do not delete this override: ModelObjectGson registers an adapter for ExternalId that
// delegates here, and the inherited reflective createModelObject would call that
// adapter straight back into this class.
public static final class JsonUtil extends AbstractModelObject.JsonUtil<ExternalId> {

public JsonUtil() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ public ExternalUrl build() {
* JsonUtil class for building {@link ExternalUrl} instances.
*/
@SuppressWarnings("unchecked")
// Do not delete this override: ModelObjectGson registers an adapter for ExternalUrl that
// delegates here, and the inherited reflective createModelObject would call that
// adapter straight back into this class.
public static final class JsonUtil extends AbstractModelObject.JsonUtil<ExternalUrl> {

public JsonUtil() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import se.michaelthelin.spotify.enums.ModelObjectType;
import se.michaelthelin.spotify.model_objects.miscellaneous.AudioAnalysis;
import se.michaelthelin.spotify.model_objects.specification.Album;
import se.michaelthelin.spotify.model_objects.specification.ExternalId;
import se.michaelthelin.spotify.model_objects.specification.Playlist;
import se.michaelthelin.spotify.model_objects.specification.Recommendations;

Expand Down Expand Up @@ -80,8 +81,28 @@ public void albumTypeAndNestedObjectsResolve() throws IOException {

assertEquals(AlbumType.ALBUM, album.getAlbumType());
assertEquals(ModelObjectType.ALBUM, album.getType());
assertNotNull(album.getExternalUrls().get("spotify"));
assertNotNull(album.getArtists()[0].getName());
assertNotNull(album.getTracks().getItems()[0].getName());
}

/**
* A map-shaped model object keeps a hand-written parser, because its JSON is the map itself
* rather than one key per field. Reflection would map the sole field to an "external_urls" or
* "external_ids" key that is not there and hand back a null map.
* <p>
* ExternalUrl is nested in most responses, so {@code ModelObjectGson} also registers an adapter
* that delegates to the same parser. ExternalId currently has no model object holding it, which
* is why it is read here on its own.
*/
@Test
public void mapShapedObjectsAreReadByTheirOwnParser() throws IOException {
Album album = new Album.JsonUtil()
.createModelObject(fixture("requests/data/albums/GetAlbumRequest.json"));

assertEquals("https://open.spotify.com/album/0sNOF9WDwhWunNAHPD3Baj",
album.getExternalUrls().get("spotify"));

ExternalId externalId = new ExternalId.JsonUtil().createModelObject("{\"upc\": \"5099749994324\"}");
assertEquals("5099749994324", externalId.getExternalIds().get("upc"));
}
}