Overview
With a new datasource class wired up and ready to be called, we can connect some dots in our datafetcher method.
In this lesson, we will:
- Connect our datafetcher to our datasource
- Account for unfamiliar JSON properties in the REST API response
- Map JSON objects into Java classes we can manipulate and reason about
Consuming a datasource
We now have an easy way to call out to the Spotify API for featured playlist data and return it in a PlaylistCollection
instance.
public PlaylistCollection featuredPlaylistsRequest() {return client.get().uri("/browse/featured-playlists").retrieve().body(PlaylistCollection.class);}
So...will running this query return real data?
query FeaturedPlaylists {featuredPlaylists {idnamedescription}}
Not quite! When this query is executed, the first thing DGS does is look for the corresponding featuredPlaylists
datafetcher method. In this case, that's the featuredPlaylists
method inside of PlaylistDataFetcher
.
Of course, this method is still just returning hardcoded data. It can't call any of the methods in SpotifyClient
because it doesn't know about it yet. Quite the dead end!
@DgsQuerypublic List<MappedPlaylist> featuredPlaylists() {MappedPlaylist rockPlaylist = new MappedPlaylist();rockPlaylist.setId("1");rockPlaylist.setName("Rock n' Roll");rockPlaylist.setDescription("A rock n' roll playlist");MappedPlaylist popPlaylist = new MappedPlaylist();popPlaylist.setId("2");popPlaylist.setName("Pop");popPlaylist.setDescription("A pop playlist");return List.of(rockPlaylist, popPlaylist);}
Let's jump back into datafetchers/PlaylistDataFetcher
to fix that.
Using SpotifyClient
in the datafetcher
We'll import SpotifyClient
, and give our class a spotifyClient
property. Then, we'll include a constructor that wires everything up on the class.
// other importsimport com.example.soundtracks.datasources.SpotifyClient;import org.springframework.beans.factory.annotation.Autowired;@DgsComponentpublic class PlaylistDataFetcher {private final SpotifyClient spotifyClient;@Autowiredpublic PlaylistDataFetcher(SpotifyClient spotifyClient) {this.spotifyClient = spotifyClient;}// featuredPlaylists method}
In our class' featuredPlaylists
method (the one that DGS is going to call for the Query.featuredPlaylists
field!), we'll first clean up our mocks.
@DgsQuerypublic List<MappedPlaylist> featuredPlaylists() {- MappedPlaylist rockPlaylist = new MappedPlaylist();- rockPlaylist.setId("1");- rockPlaylist.setName("Rock n' Roll");- rockPlaylist.setDescription("A rock n' roll playlist");- MappedPlaylist popPlaylist = new MappedPlaylist();- popPlaylist.setId("2");- popPlaylist.setName("Pop");- popPlaylist.setDescription("A pop playlist");-- return List.of(rockPlaylist, popPlaylist);}
Next, we'll call the featuredPlaylistsRequest
method our class' spotifyClient
instance.
@DgsQuerypublic List<MappedPlaylist> featuredPlaylists() {spotifyClient.featuredPlaylistsRequest();}
We know that featuredPlaylistsRequest
returns data in a PlaylistCollection
instance, so let's import it:
import com.example.soundtracks.models.PlaylistCollection;
And apply it to the response we get from calling our SpotifyClient
instance.
@DgsQuerypublic List<MappedPlaylist> featuredPlaylists() {PlaylistCollection response = spotifyClient.featuredPlaylistsRequest();}
Next, we want to actually get the list of playlists from the response. We wrote our PlaylistCollection
class' getPlaylists
method to grab this data exactly.
@DgsQuerypublic List<MappedPlaylist> featuredPlaylists() {PlaylistCollection response = spotifyClient.featuredPlaylistsRequest();return response.getPlaylists();}
Time to return to Sandbox! First, let's make sure that we restart our server so that we're running the latest changes.
Now let's navigate back to the Explorer. Check that your connection to http://localhost:8080/graphql
is still valid, then let's rerun our query.
Aaaaand, drumroll...
query FeaturedPlaylists {featuredPlaylists {idnamedescription}}
UH-OH! Another error!
Here's the message in our terminal:
"JSON parse error: Unrecognized field "collaborative"(class com.example.soundtracks.models.MappedPlaylist), not marked as ignorable"
We're seeing this error because our MappedPlaylist
class, inheriting from the generated Playlist
class, only knows how to handle three specific fields: id
, name
, and description
.
But we're attempting to take a list of much larger JSON objects, with many more properties, and convert each into an instance of the MappedPlaylist
class. When we inspect one of these playlist objects from our REST endpoint, we can see that "collaborative"
is its very first property. But our MappedPlaylist
class doesn't know what to do with it!
The JsonIgnoreProperties
annotation
The Jackson package gives us an annotation—JsonIgnoreProperties
—to handle this very scenario. By setting @JsonIgnoreProperties
on a class, and passing it an argument of ignoreUnknown = true
, we can instruct our class to ignore any unfamiliar JSON properties it encounters, and handle what it does recognize.
@JsonIgnoreProperties(ignoreUnknown = true)public class HappilyIgnoresUnknownProperties {// class properties, getters, setters}
Let's apply this annotation to our MappedPlaylist
class to take care of the error.
// other importsimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;@JsonIgnoreProperties(ignoreUnknown = true)public class MappedPlaylist extends Playlist {}
And with that, let's again restart our server to see the effect of adding this annotation.
Run the query, fetch the data!
Back in the Explorer, let's try that query again.
query FeaturedPlaylists {featuredPlaylists {idnamedescription}}
In the Response panel, we should now see actual data from our REST API!
{"data": {"featuredPlaylists": [{"id": "6Fl8d6KF0O4V5kFdbzalfW","name": "Sweet Beats & Eats","description": "Tooth-achingly sweet beats for your sweet eats"},{"id": "20RU4pHDte01QywpOL6ifh","name": "Grilling Tunes","description": "Set the barbecue mood. Upbeat and laid-back tracks complement the sizzle of the grill, turning your outdoor cooking sessions into a flavorful experience. For those who savor good music and great food, it's the perfect playlist"},{"id": "6LB6g7S5nc1uVVfj00Kh6Z","name": "Zesty Culinary Harmony","description": "Infuse flavor into your kitchen. This playlist merges zesty tunes with culinary vibes, creating a harmonious background for your cooking escapades. Feel the synergy between music and the zest of your creations."}]}}
Key takeaways
- Datafetcher methods can connect directly to the data sources in our GraphQL API
- We can extend the generated classes DGS gives us with additional logic to better suit the needs of our API
- The
JsonIgnoreProperties
annotation allows us to ignore unfamiliar JSON properties when instantiating a new class
Up next
Amazing! We've translated the REST API response to the types that we defined in the schema, and we have a seamless querying experience. But right now, our list of playlists is pretty limited. Based on our mockups, we need to start exploring some additional functionality—such as querying for additional playlist properties (hello, tracks!) or asking for a particular playlist. Let's uplevel our GraphQL API in the next lesson.
Share your questions and comments about this lesson
This course is currently in
You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.