Search code examples
jsonf#type-providers

How do I read JSON from a URL with F# JsonProvider


I was watching a Channel 9 video on "Why you should use F#" and was impressed at how easy it was to pull data off Wikipedia. For example, he showed the following code that listed out the different appearances of Dr Who...

[<Literal>]
let url = @"https://en.wikipedia.org/wiki/Doctor_Who"
type DoctorWhoData = HtmlProvider<url>

let data = DoctorWhoData.GetSample()
let app = data.Tables.``Changes of appearance``.Rows

As he typed, he had Intellisense on that last line, which took the guesswork out of finding the data.

I recently had a requirement to pull some data from the Google Maps API. For example, the following URL pulls data for the UK postcode SW1A 1AA (Buckingham Palace in case any is interested!)...

http://maps.google.com/maps/api/geocode/json?address=sw1a+1aa+uk

Given that the data is just Json, I thought it should be equally easy to pull information from that using F#. However, I got stuck trying to get the latitude and longitude.

I started off with the following...

type GoogleData = JsonProvider<"http://maps.google.com/maps/api/geocode/json?address=sw1a+1aa+uk">

...but then got stuck trying to extract the data. The json contains an array with one entry. That contains a geometry node, which contains a location node that holds the two values. In pseudo code, I would have expected to do something like this...

let lat = GoogleData.GetSample().[0].geometry.location.lat

...however, this didn't work. I tried it without GetSample(), but didn't get much further. Intellisense showed me things that didn't seem to match the Json at all...enter image description here

Anyone able to advise where I'm going wrong? How do I find the data? More to the point, how do I get Intellisense to help me? I managed to do this in C#, but it was very hit-and-miss as I had to use a dynamic object, so didn't get Intellisense. Based on what I saw in that video, I was hoping to get Intellisense here.


Solution

  • The JSON response contains 2 top level objects, results and status.

    {
       "results" : [
          {
             "address_components" : [
                {
                   "long_name" : "SW1A 1AA",
                   "short_name" : "SW1A 1AA",
                   "types" : [ "postal_code" ]
        //snip
        ],
        "status" : "OK"
    }
    

    The lat and long are under results so you need to follow that down from .GetSample()

    let lat = GoogleData.GetSample().Results.[0].Geometry.Location.Lat
    

    To use data other than the sample data use the Load function:

    let honley = GoogleData.Load "http://maps.google.com/maps/api/geocode/json?address=honley+uk"
    let honley_latlong = honley.Results.[0].Geometry.Location
    
    //{
    //  "lat": 53.602267,
    //  "lng": -1.794173
    //}
    printfn "%A" honley_latlong