Exploring MusicKit: Genres
Book

Exploring Freelancing

Navigate freelancing as a developer; find clients, manage contracts, ensure timely payment, and learn from experiences!

From the Wikipedia definition:

A music genre is a conventional category that identifies some pieces of music as belonging to a shared tradition or set of conventions. It is distinguished from musical form and musical style, although these terms are sometimes used interchangeably in practice.

Genres provide you with different categories in Apple Music that contains a collection of songs.

For example, I am currently listening to On the Train Ride Home by The Paper Kites, under the Alternative genre. In fact, most of the artists’ albums are under the Alternative genre.

Apple Music API

The Apple Music API provides us with three endpoints when writing this article. They help you to get information about the genres of the user’s music or items in the Apple Music Catalog.

Genres

Before using the endpoints, let us first understand the Genres object. It is a resource object that represents a music genre. It has four properties:

  • id - The identifier for the genre.
  • type - It defines the type of the object. In this case, it is always genres.
  • href - The relative location for the genre resource.
  • attributes - The attributes for the genre.

Attributes

There are three attributes for each genre:

  • name - The localized name of the genre
  • parentId - The identifier of the parent for the genre.
  • parentName - The localized name of the parent genre.

GenresResponse

It is the response that we get from hitting the above endpoints. The GenresResponse only has one property:

  • data - An array of Genres objects included in response to the request.

Genre in MusicKit

MusicKit for Swift contains a Genre structure that is equivalent to the Attributes of the Genre object. It contains the following instance properties:

  • id - The unique identifier for the genre. It is of the type MusicItemID.
  • name: The localized name of the genre.
  • parent: The parent genre, if any. It is an optional Genre.

Genres Structure

While MusicKit provides you with Genre, it is not enough to decode the response that we get from the endpoints. We need another structure that holds the array of Genres as the data property. So, you can use this custom structure in your code that conforms to Decodable:

struct Genres: Decodable {
    let data: [Genre]
}

Now that we know what is required let us explore the endpoints!

Get a Catalog Genre

This endpoint fetches a genre using its identifier. You provide the unique identifier and storefront as the path parameters.

https://api.music.apple.com/v1/catalog/{storefront}/genres/{id}

For example, hitting this endpoint for the Bollywood genre in Indian storefront:

https://api.music.apple.com/v1/catalog/in/genres/1263

We get the following response:

{
    "data": [
        {
            "id": "1263",
            "type": "genres",
            "href": "/v1/catalog/in/genres/1263",
            "attributes": {
                "name": "Bollywood",
                "parentId": "1262",
                "parentName": "Indian"
            }
        }
    ]
}

To get the same data using MusicKit for Swift, we use the MusicDataRequest that helps us load data from arbitrary Apple Music API endpoints like the ones mentioned above.

We get the same unstructured JSON response and decode it using JSONDecoder().

struct Genres: Decodable {
    let data: [Genre]
}

do {
    let genreID = "1263"
    let countryCode = try await MusicDataRequest.currentCountryCode
    
    let genreURL = "https://api.music.apple.com/v1/catalog/\(countryCode)/genres/\(genreID)"
    
    guard let url = URL(string: genreURL) else {
        throw URLError(.badURL)
    }
    
    let request = MusicDataRequest(urlRequest: URLRequest(url: url))
    let response = try await request.response()
    
    let genre = try JSONDecoder().decode(Genres.self, from: response.data)
    print(genre)
    
} catch {
    print(error)
}      

The response in the console is:

Genres(data: [Genre(id: "1263", name: "Bollywood", parent: Genre(id: "1262", name: "Indian"))])

Get Multiple Catalog Genres

This endpoint fetches multiple genres using their identifiers. You provide the unique identifiers as query parameters and storefront as the path parameters.

https://api.music.apple.com/v1/catalog/{storefront}/genres/{id}

For example, hitting this endpoint for the Bollywood and Indian Classical genre in Indian storefront:

https://api.music.apple.com/v1/catalog/in/genres?ids=1263,1269

We get the following response:

{
    "data": [
        {
            "id": "1263",
            "type": "genres",
            "href": "/v1/catalog/in/genres/1263",
            "attributes": {
                "name": "Bollywood",
                "parentName": "Indian",
                "parentId": "1262"
            }
        },
        {
            "id": "1269",
            "type": "genres",
            "href": "/v1/catalog/in/genres/1269",
            "attributes": {
                "name": "Indian Classical",
                "parentName": "Indian",
                "parentId": "1262"
            }
        }
    ]
}

To use it in MusicKit for Swift:

do {
    let genreIDs = ["1263", "1269"]
    let countryCode = try await MusicDataRequest.currentCountryCode
    
    let genreURL = "https://api.music.apple.com/v1/catalog/\(countryCode)/genres?ids=\(genreIDs.joined(separator: ","))"
    
    guard let url = URL(string: genreURL) else {
        throw URLError(.badURL)
    }
    
    let request = MusicDataRequest(urlRequest: URLRequest(url: url))
    let response = try await request.response()
    
    let genre = try JSONDecoder().decode(Genres.self, from: response.data)
    print(genre)
    
} catch {
    print(error)
}      

The response in the console is:

Genres(data: [Genre(id: "1263", name: "Bollywood", parent: Genre(id: "1262", name: "Indian")), Genre(id: "1269", name: "Indian Classical", parent: Genre(id: "1262", name: "Indian"))])

Get Catalog Top Charts Genres

This endpoint fetches all genres for the current top charts. You provide the unique identifiers as query parameters and storefront as the path parameters.

https://api.music.apple.com/v1/catalog/{storefront}/genres/

For example, hitting this endpoint with a limit of five genres in Indian storefront:

https://api.music.apple.com/v1/catalog/in/genres?limit=5

We get the following response:

{
    "next": "/v1/catalog/in/genres?offset=5",
    "data": [
        {
            "id": "34",
            "type": "genres",
            "href": "/v1/catalog/in/genres/34",
            "attributes": {
                "name": "Music"
            }
        },
        {
            "id": "20",
            "type": "genres",
            "href": "/v1/catalog/in/genres/20",
            "attributes": {
                "name": "Alternative",
                "parentId": "34",
                "parentName": "Music"
            }
        },
        {
            "id": "1263",
            "type": "genres",
            "href": "/v1/catalog/in/genres/1263",
            "attributes": {
                "name": "Bollywood",
                "parentId": "1262",
                "parentName": "Indian"
            }
        },
        {
            "id": "5",
            "type": "genres",
            "href": "/v1/catalog/in/genres/5",
            "attributes": {
                "name": "Classical",
                "parentId": "34",
                "parentName": "Music"
            }
        },
        {
            "id": "17",
            "type": "genres",
            "href": "/v1/catalog/in/genres/17",
            "attributes": {
                "name": "Dance",
                "parentId": "34",
                "parentName": "Music"
            }
        }
    ]
}

We got the next key when adding the limit parameter. How do we access it?

MusicItemCollection

While the above method of creating a custom structure was discussed in the session Meet MusicKit for Swift, these cases of pagination are not mentioned.

After experimenting over the months, I realized that you could directly use the MusicItemCollection for this request. Genre conforms to MusicItem protocol which is the requirement for MusicItemCollection:

struct MusicItemCollection<MusicItemType> where MusicItemType : MusicItem

So, we can remove the custom structure Genres and replace it with the following:

typealias Genres = MusicItemCollection<Genre>

All the fetch methods remain the same, but now we have access to the next batch of items if you want to paginate the genres response:

typealias Genres = MusicItemCollection<Genre>

do {
    let countryCode = try await MusicDataRequest.currentCountryCode
    let genreURL = "https://api.music.apple.com/v1/catalog/\(countryCode)/genres?limit=5"
    
    guard let url = URL(string: genreURL) else {
        throw URLError(.badURL)
    }
    
    let request = MusicDataRequest(urlRequest: URLRequest(url: url))
    let response = try await request.response()
    
    let genre = try JSONDecoder().decode(Genres.self, from: response.data)
    print(genre)
    
} catch {
    print(error)
}

The response in the console is:

MusicItemCollection<Genre>(
  items: [
    Genre(id: "34", name: "Music"),
    Genre(id: "20", name: "Alternative"),
    Genre(id: "1263", name: "Bollywood"),
    Genre(id: "5", name: "Classical"),
    Genre(id: "17", name: "Dance")
  ],
  hasNextBatch: true
)

To fetch the next batch of collections, you can use the instance method nextBatch(limit:) on MusicItemCollection:

if genres.hasNextBatch {
    guard let nextGenres = try await genres.nextBatch(limit: 5) else { return }
    print(nextGenres)
}

The response in the console is:

MusicItemCollection<Genre>(
  items: [
    Genre(id: "1267", name: "Devotional & Spiritual"),
    Genre(id: "7", name: "Electronic"),
    Genre(id: "18", name: "Hip-Hop/Rap"),
    Genre(id: "1269", name: "Indian Classical"),
    Genre(id: "1185", name: "Indian Pop")
  ],
  hasNextBatch: true
)

Getting Associated Genres

To get the associated genres for music videos, songs, albums, or artists, we take advantage of the PartialMusicProperty .

For example, to get the genre of the song On the Train Ride Home, we use the with(_:) instance method:

let songRequest = MusicCatalogResourceRequest<Song>(matching: \.id, equalTo: "1370781005")
let songResponse = try await songRequest.response()

guard let song = songResponse.items.first else { return }

print(song)
print(song.genres)

let detailedSong = try await song.with([.genres])

print(detailedSong)

guard let genres = detailedSong.genres else { return }

print(genres)

The response in the console is:

Song(id: "1370781005", title: "On the Train Ride Home", artistName: "The Paper Kites")
nil
Song(id: "1370781005", title: "On the Train Ride Home", artistName: "The Paper Kites")
MusicItemCollection<Genre>(
  items: [
    Genre(id: "20", name: "Alternative"),
    Genre(id: "34", name: "Music")
  ]
)

Note that the genre is omitted by default, and you have to make another request to access it using the genres property. Alternatively, you can directly get the names of the genres.

let songRequest = MusicCatalogResourceRequest<Song>(matching: \.id, equalTo: "1370781005")
let songResponse = try await songRequest.response()

guard let song = songResponse.items.first else { return }

print(song.genreNames)

The response in the console is:

["Alternative", "Music"]

Dump the Genre

If you have made it this far, there is a surprise! You only get limited information when you use the print().

But, if you use the dump() method instead, you get the whole hierarchy of information. Although it isn’t much of use, we understand that more available properties are associated with a Genre structure.

The dump is a big file, so I am redacting the details.

Console output ``` ▿ Genre(id: "1263", name: "Bollywood", parent: Genre(id: "1262", name: "Indian")) ▿ id: 1263 - rawValue: "1263" ▿ propertyProvider: MusicKit.AnyPropertyProvider ▿ wrappedPropertyProvider: MusicKit.GenrePropertyProvider - artwork: nil - editorialArtworks: nil - editorialNotes: nil - mainUberArtwork: nil ▿ name: Optional("Bollywood") - some: "Bollywood" ▿ parent: Optional(MusicKit.RelatedItem.regular(Genre(id: "1262", name: "Indian"))) ▿ some: MusicKit.RelatedItem.regular ▿ regular: Genre(id: "1262", name: "Indian") ▿ id: 1262 - rawValue: "1262" ▿ propertyProvider: MusicKit.AnyPropertyProvider ▿ wrappedPropertyProvider: MusicKit.GenrePropertyProvider - artwork: nil - editorialArtworks: nil - editorialNotes: nil - mainUberArtwork: nil ▿ name: Optional("Indian") - some: "Indian" - parent: nil - url: nil - playlists: nil ▿ knownProperties: 8 elements - MusicAttributeProperty<Genre, Artwork>("artwork") #0 - super: MusicKit.PartialMusicProperty ▿ super: MusicKit.AnyMusicProperty - name: "artwork" - propertyKind: MusicKit.AnyMusicProperty.PropertyKind.attribute - MusicExtendedAttributeProperty<Genre, EditorialArtworks>("editorialArtwork") #1 - super: MusicKit.PartialMusicAsyncProperty - super: MusicKit.PartialMusicProperty ▿ super: MusicKit.AnyMusicProperty - name: "editorialArtwork" - propertyKind: MusicKit.AnyMusicProperty.PropertyKind.extendedAttribute - MusicAttributeProperty<Genre, EditorialNotes>("editorialNotes") #2 - super: MusicKit.PartialMusicProperty ▿ super: MusicKit.AnyMusicProperty - name: "editorialNotes" - propertyKind: MusicKit.AnyMusicProperty.PropertyKind.attribute - MusicAttributeProperty<Genre, Artwork>("uber.masterArt") #3 - super: MusicKit.PartialMusicProperty ▿ super: MusicKit.AnyMusicProperty - name: "uber.masterArt" - propertyKind: MusicKit.AnyMusicProperty.PropertyKind.attribute - MusicAttributeProperty<Genre, String>("name") #4 - super: MusicKit.PartialMusicProperty ▿ super: MusicKit.AnyMusicProperty - name: "name" - propertyKind: MusicKit.AnyMusicProperty.PropertyKind.attribute - MusicAttributeProperty<Genre, Genre>("_parent") #5 - super: MusicKit.PartialMusicProperty ▿ super: MusicKit.AnyMusicProperty - name: "_parent" - propertyKind: MusicKit.AnyMusicProperty.PropertyKind.attribute - MusicAttributeProperty<Genre, URL>("url") #6 - super: MusicKit.PartialMusicProperty ▿ super: MusicKit.AnyMusicProperty - name: "url" - propertyKind: MusicKit.AnyMusicProperty.PropertyKind.attribute ▿ MusicRelationshipProperty<Genre, Playlist>("playlists") #7 - super: MusicKit.PartialMusicAsyncProperty - super: MusicKit.PartialMusicProperty ▿ super: MusicKit.AnyMusicProperty - name: "playlists" ▿ propertyKind: MusicKit.AnyMusicProperty.PropertyKind.relationship ▿ relationship: (1 element) - relatedItemTypeDescription: "Playlist" - kind: MusicKit.MusicRelationshipPropertyKind.model ▿ type: "genres" - rawValue: "genres" - isLibraryType: false - href: "/v1/catalog/in/genres/1262" - rawAttributes: 0 key/value pairs - rawRelationships: 0 key/value pairs - rawAssociations: 0 key/value pairs - url: nil - playlists: nil ▿ knownProperties: 8 elements - MusicAttributeProperty<Genre, Artwork>("artwork") #0 - MusicExtendedAttributeProperty<Genre, EditorialArtworks>("editorialArtwork") #1 - MusicAttributeProperty<Genre, EditorialNotes>("editorialNotes") #2 - MusicAttributeProperty<Genre, Artwork>("uber.masterArt") #3 - MusicAttributeProperty<Genre, String>("name") #4 - MusicAttributeProperty<Genre, Genre>("_parent") #5 - MusicAttributeProperty<Genre, URL>("url") #6 ▿ MusicRelationshipProperty<Genre, Playlist>("playlists") #7 ▿ type: "genres" - rawValue: "genres" - isLibraryType: false - href: "/v1/catalog/in/genres/1263" - rawAttributes: 0 key/value pairs - rawRelationships: 0 key/value pairs - rawAssociations: 0 key/value pairs ``` </details>

One interesting `MusicRelationshipProperty<Genre, Playlist>` between the genre and playlist is not mentioned anywhere in the documentation. And I cannot access it either. While I am still exploring how to access it, let me know if you do! ## Conclusion This article was an exploration of using genres in MusicKit for Swift. You can use the Charts endpoint to get charts for a particular genre, which I will discuss in another article soon. Tag [@rudrankriyam](https://twitter.com/rudrankriyam) on Twitter if you have experience working with genres and want to spread some knowledge around! Thanks for reading, and I hope you're enjoying it!
Book

Exploring Freelancing

Navigate freelancing as a developer; find clients, manage contracts, ensure timely payment, and learn from experiences!

Written by

Rudrank Riyam

Hi, my name is Rudrank. I create apps for Apple Platforms while listening to music all day and night. Author of "Exploring MusicKit". Apple WWDC 2019 scholarship winner.