What can you learn here?
- Library of Rest Calls
- How to add new entries
- How to query from data
Adding more content to the rest library get it now
Now that we have a generalized mechanism to easily populate Excel tables from Rest queries in Rest to Excel library, let’s take a look at extending the known list of rest apis, or make one off rest calls.
Try it out first ?These are enhancements to Data Manipulation Classes so they have been included in cDataSet.xlsm, which is downloadable from Download Complete Projects.
Library structure
The ‘library is actually just a few lines of code in the restLibrary module that define how to deal with a given rest request and its response. Here are some entries
<pre>Public Function createRestLibrary() As cJobject
' this creates the restlibrary as a jSon object
Dim cj As cJobject
Set cj = New cJobject
cj.init Nothing, "restLibrary"
With cj
With .add("google patents")
.add "restType", erSingleQuery
.add "url", "https://ajax.googleapis.com/ajax/services/search/patent?v=1.0&rsz=8&q="
.add "results", "responseData.results"
.add "treeSearch", False
.add "ignore", vbNullString
End With
With .add("twitter")
.add "restType", erSingleQuery
.add "url", "http://search.twitter.com/search.json?q="
.add "results", "results"
.add "treeSearch", False
.add "ignore", vbNullString
End With
With .add("google books by isbn")
.add "restType", erQueryPerRow
.add "url", "https://www.googleapis.com/books/v1/volumes?q=isbn:"
.add "results", "Items"
.add "treeSearch", True
.add "ignore", vbNullString
End With
With .add("yahoo geocode")
.add "restType", erQueryPerRow
.add "url", "http://where.yahooapis.com/geocode?flags=J&location="
.add "results", "resultset.results"
.add "treeSearch", True
.add "ignore", vbNullString
End With
With .add("imdb by title")
.add "restType", erQueryPerRow
.add "url", "http://www.imdbapi.com/?tomatoes=true&t="
.add "results", vbNullString
.add "treeSearch", False
.add "ignore", vbNullString
End With
With .add("itunes movie")
.add "restType", erSingleQuery
.add "url", "http://itunes.apple.com/search?entity=movie&media=movie&term="
.add "results", "results"
.add "treeSearch", False
.add "ignore", vbNullString
End With
With .add("google finance")
.add "restType", erQueryPerRow
.add "url", "http://www.google.com/finance/info?infotype=infoquoteall&q="
.add "results", "crest"
.add "treeSearch", True
.add "ignore", vbLf & "//"
End With
With .add("whatthetrend")
.add "restType", erSingleQuery
.add "url", "http://api.whatthetrend.com/api/v2/trends.json"
.add "results", "trends"
.add "treeSearch", False
.add "ignore", vbNullString
End With
With .add("tweetsentiments")
.add "restType", erQueryPerRow
.add "url", "http://data.tweetsentiments.com:8080/api/analyze.json?q="
.add "results", "sentiment"
.add "treeSearch", False
.add "ignore", vbNullString
End With
With .add("topsy histogram")
.add "restType", erQueryPerRow
.add "url", "http://otter.topsy.com/searchhistogram.json?period=30&q="
.add "results", "response"
.add "treeSearch", True
.add "ignore", vbNullString
End With
With .add("topsy count")
.add "restType", erQueryPerRow
.add "url", "http://otter.topsy.com/searchcount.json?q="
.add "results", "response"
.add "treeSearch", True
.add "ignore", vbNullString
End With
With .add("tweetsentiment topics")
.add "restType", erQueryPerRow
.add "url", "http://data.tweetsentiments.com:8080/api/search.json?topic="
.add "results", ""
.add "treeSearch", True
.add "ignore", vbNullString
End With
With .add("tweetsentiment details")
.add "restType", erSingleQuery
.add "url", "http://data.tweetsentiments.com:8080/api/search.json?topic="
.add "results", "results"
.add "treeSearch", True
.add "ignore", vbNullString
End With
End With
Set createRestLibrary = cj
End Function</pre>
Twitter rest walkthrough
There are 2 types of queries current implemented, as illustrated in the following code
<pre>Private Function whichType(t As erRestType) As String
Select Case t
Case erSingleQuery
whichType = " single query that can return multiple rows"
Case erQueryPerRow
whichType = " a single column provides the query data for each row"
Case Default
Debug.Assert False
End Select
End Function</pre>
The twitter example is of type erSingleQuery
which means that a single query will return multiple rows that will populate the designated sheet. It is possible, but unlikely, to change the behavior of a given library entry through arguments to the restquery function. A typical call would be
<pre>Dim cr As New cRest
Set cr = restQuery(sheetName, libEntry, queryString)</pre>
but there are some other optional behavior changing arguments.
<pre>Public Function restQuery(sName As String, _
Optional sEntry As String = vbNullString, _
Optional sQuery As String = vbNullString, _
Optional sQueryColumn As String = vbNullString, _
Optional sRestUrl As String = vbNullString, _
Optional sResponseResults As String = vbNullString, _
Optional bTreeSearch As Boolean = True, _
Optional bPopulate As Boolean = True, _
Optional bClearMissing As Boolean = True, _
Optional complain As Boolean = True _
) As cRest</pre>
So the first step is to set the default behavior for the query type, and check that any behavior changes asked for were meant, and setup where to find the API and deal with the response.
This is saying that the twitter case is a singleQuery, and setting the URL of the rest query ( you can find this by searching around), that the data portion of the response can be found in the branches under ‘results’, and that any field names requested to be extracted can be found at the top level (in other words don’t go searching down a deeper level). We’ll look at a response format shortly.
To add something to the library, you only need to add a few lines like this to createRestLibrary().
<pre>With .add("twitter")
.add "restType", erSingleQuery
.add "url", "http://search.twitter.com/search.json?q="
.add "results", "results"
.add "treeSearch", False
.add "ignore", vbNullString
End With</pre>
since this is held as a cJObject you can serialize that back as jSon.
<pre>?createRestLibrary().child("Twitter").serialize(True)
{
"twitter":{
"restType":"1",
"url":"http://search.twitter.com/search.json?q=",
"results":"results",
"treeSearch":"False",
"ignore":""
}
}</pre>
so this function will warn of any request change to that default behavior and abandon if it’s some kind of calling error.
<pre>Private Function abandonType(sEntry, qType As erRestType, targetType As erRestType) As Boolean
If qType <> targetType Then
abandonType = Not (vbYes = MsgBox(sEntry & " is normally " & _
whichType(targetType) & _
" but you have specified " & _
whichType(qType) & ": try anyway?", vbYesNo))
Else
abandonType = False
End If
End Function</pre>
Finally we initialize a cRest class
<pre>Set cr = New cRest
Set cr = cr.init(sResults, qType, .HeadingRow.Exists(sQueryColumn), _
, dSet, bPopulate, sUrl, bClearMissing, bTreeSearch, complain)</pre>
and then execute it
<pre>Set cr = cr.execute(sQuery)</pre>
Finding out the available fields in a response
If you have correctly identifed the Rest URL, you can just enter it into a browser with a test query and you will get some result.
Obviously you can search around for the API documentation, but it usually only takes a few moments to figure out which columns you need, and how to find them.
Here is a snippet of the response to http://search.twitter.com/search.json?q=excel
Response
<pre>{"completed_in":0.087,"max_id":138696554059010048,"max_id_str":"138696554059010048","next_page":"?page=2&max_id=138696554059010048&q=excel","page":1,"query":"excel","refresh_url":"?since_id=138696554059010048&q=excel","results":[{"created_at":"Mon, 21 Nov 2011 19:13:30 +0000","from_user":"DaspetArancha","from_user_id":308114420,"from_user_id_str":"308114420","from_user_name":"Arancha Daspet","geo":null,"id":138696554059010048,"id_str":"138696554059010048","iso_language_code":"es","metadata":{"result_type":"recent"},"profile_image_url":"http://a3.twimg.com/profile_images/1641772688/image__66__normal.jpg","source":"<a href="http://twitter.com/">web</a>","text":"Esto del nuevo gobierno es un asco... no dejo de rechazar ofertas de trabajo y sigo sin entender el Excel !!! #necesitoayudainformatica","to_user_id":null,"to_user_id_str":null},{"created_at":"Mon, 21 Nov 2011 19:13:08 +0000","from_user":"AllenDiirks","from_user_id":393955623,"from_user_id_str":"393955623","from_user_name":"Allen Diirks","geo":null,"id</pre>
From this we can deduce that the data we need are in this kind of structure
<pre>"results":[ {} , {} , {} ... ]</pre>
This is a very simple, flat response, so all data items can be found at the first level. For something more complicated we can set bTreeSearch = True
, which will indicate the the data item could be somewhere deeper in the tree. In our example we have created an Excel sheet with names corresponding to the data fields we want to extract.
Setting column names that correspond to the field names in the response,will indicate that you want those filled up. Any names not recognized will be ignored. This means that you can use this code to partially fill in a table.
Yahoo Geocode walkthrough
Elsewhere on this site I covered more specific ways of geocoding for each of Ovi, Google, Yahoo and Bing. Now that I have it, I could have used this Rest library to do this, and I may go back and update those at a future date. The library entry for yahoo geocode looks like this
<pre>With .add("yahoo geocode")
.add "restType", erQueryPerRow
.add "url", "http://where.yahooapis.com/geocode?flags=J&location="
.add "results", "resultset.results"
.add "treeSearch", True
.add "ignore", vbNullString
End With</pre>
The main difference between this and the twitter example, is that we are going to use each row in our input dataset as a separate query, picking up the query data from the contents of the sheet. The input sheet looks like this
I have reused some test data from one of the Data Driven Mapping applications examples, and selected a few items from the yahoo response for columns to populate.
This Yahoo query http://where.yahooapis.com/geocode?flags=J&location=buckingham%20palace
returns this response
<pre>{"ResultSet":{"version":"1.0","Error":0,"ErrorMessage":"No error","Locale":"us_US","Quality":90,"Found":1,"Results":[{"quality":90,"latitude":"51.501171","longitude":"-0.142460","offsetlat":"51.501171","offsetlon":"-0.142460","radius":100,"name":"Buckingham Palace","line1":"Buckingham Palace","line2":"London","line3":"SW1A 1","line4":"United Kingdom","house":"","street":"","xstreet":"","unittype":"","unit":"","postal":"SW1A 1","neighborhood":"","city":"London","county":"Greater London","state":"England","country":"United Kingdom","countrycode":"GB","statecode":"ENG","countycode":"LND","uzip":"SW1A 1","hash":"","woeid":22474234,"woetype":20}]}}</pre>
From this we can deduce
<pre>sResults = "resultset.results"
bTreeSearch = True</pre>
The Yahoo response provides an overall envelop of resultset, and within that, the actual results are in another envelope called results. Together this gives us where to find the data, namely
<pre>resultset.results</pre>
Since resultset.results is itself an array (because ambiguous queries can result in multiple results), then the field latitude, for example, is not actually at the top level (resultset.results.latitude), but would need an array reference such as
<pre>resultset.results.1.latitude.</pre>
All this unpleasantness can easily be avoided by just setting bTreeSearch = True since this will find ‘latitude’ wherever it is, and we get this result, all nicely geocoded.
Summary
Adding your own frequently used rest queries is pretty straightforward then. All you have to do is
- find the Rest URl , try it out, and understand what the response looks like
- think of a name
- add it to the restLibrary module along with the 4 lines of code you can tweak from other entries
- Why not submit it to our forum for inclusion in the library so that others can benefit from your research.
Now let’s look at Adhoc Rest Queries or continue reading about Rest to Excel Library here