Getting JSON data from an API in SWI-Prolog

WE’LL USE one of my favourite API endpoints: Open Notify ISS Location Now. It’ll give us a short bit of JSON to let us know where the International Space Station is right now. It’s only the Latitude and Longitude, if you want more information than that you’ll need to add more code, which is a fun little project!

iss.pl

LET’s GET straight to the code. First we import http/json and http/http_open libraries to handle the requests and conversion to JSON for us.

:- use_module(library(http/json)).
:- use_module(library(http/http_open)).

Then I setup known/2 as a dynamic predicate, this is just for caching the data during development. We’ll use it to store the data so we can play with queries against it without spamming the API endpoint.

:- dynamic(known/2).

Often with API GET requests, parameters are encoded into the URL, so it’s a good idea to make a handy predicate to create the required URL for you, use format/3 as per the documented example. In this case, there’s no parameters, but I still kept the predicate for good code smells.

open_notify_url("http://api.open-notify.org/iss-now.json").

OK, let’s get the data and load it into a SWI-Prolog dict. Use setup_call_cleanup to ensure the clean-up is done even if the program is interrupted. We open the URL, read the data in as a dict, and close our connection. The list provided to http_open/3 are the Options, this is also where we’d change our request method to post and add post data if this API required that ([method(post), post(Data), ... ]).

%! iss_data(-Data) is det.
%  get JSON ISS location data from open notify api and read in as dict
iss_data(Data) :-
    open_notify_url(URL),
    setup_call_cleanup(
        http_open(URL, In, [request_header('Accept'='application/json')]),
        json_read_dict(In, Data),
        close(In)
    ).

We set known/2 up for caching, let’s use it.

%! cached_iss_data(-Data) is det.
%  get cached data, else make a fresh request, useful during development.
cached_iss_data(Data) :-
    known(data, Data) ;
    iss_data(Data),
    assert(known(data, Data)).

Finally, let’s extract the bit of the data we need. When using dictionaries in SWI-Prolog, the get/1 method will fail gracefully, which is handy if we don’t get the response we’re expecting.

%! iss_location(+Data, -Lat, -Long) is det.
%  extract the latitude and longitude from the data.
iss_location(Data, Lat, Long) :-
    Position = Data.get(iss_position),
    Lat = Position.get(latitude),
    Long = Position.get(longitude).

Example Usage

FIRE UP swipl and make your queries:

?- cached_iss_data(Data).
Data = _6750{iss_position:_6738{latitude:"40.8115", longitude:"-82.5002"}, message:"success", timestamp:1534434717}.

?- cached_iss_data(Data), iss_location(Data, Lat, Long).
Data = _3154{iss_position:_3178{latitude:"40.8115", longitude:"-82.5002"}, message:"success", timestamp:1534434717},
Lat = "40.8115",
Long = "-82.5002" .

Conclusion

Making requests to JSON API endpoints is not so hard in SWI-Prolog, once you’ve got a working example to start with. There’s many parameters to an API request, such as the accept header, request method, etc. In SWI-Prolog, you have to deal with all of them, just like when using curl or similar, plus you’ll have to handle the conversion of the incoming data to something you can work with in Prolog. Hopefully this example will save you the many headache’s I had in working out a working predicate. Happy JSON requesting!

p.s. I highly recommend making an ISS tracker as a hobby project, I’ve made a few and they’ve always been good fun. Next I’d be wondering if the ISS was over a country, what that country might be, and if I was really ambitious, some facts about that country.