Search code examples
jsonrequestelisp

JSON requests within Emacs, Phase 2: Getting a collection of items


I'm trying to get a list of sites (a list of a-lists) with the Stack Exchange API using request.el.

I'm making an Emacs major mode for Stack Exchange, so this has some nice potential payoff for you Emacs users out there. ;) (And since I tagged elisp, I'm assuming that's the lot of you.)

To do this, a fundamental necessity would be to make a request for JSON, and then view the list of returned sites. The StackExchange API supplies the /sites resource, and a request for that resource returns a collection of site objects like so:

{
  "items": [
    {
      "site_type": "main_site",
      "name": "Stack Overflow",
      "logo_url": "http://cdn.sstatic.net/stackoverflow/img/logo.png",
      "api_site_parameter": "stackoverflow",
      "site_url": "http://stackoverflow.com",
      "audience": "professional and enthusiast programmers",
      "icon_url": "http://cdn.sstatic.net/stackoverflow/img/apple-touch-icon.png",
      "aliases": [
        "http://www.stackoverflow.com"
      ],
      "site_state": "normal",
      "styling": {
        "link_color": "#0077CC",
        "tag_foreground_color": "#3E6D8E",
        "tag_background_color": "#E0EAF1"
      },
      "launch_date": 1221436800,
      "favicon_url": "http://cdn.sstatic.net/stackoverflow/img/favicon.ico",
      "related_sites": [
        {
          "name": "Stack Overflow Chat",
          "site_url": "http://chat.stackoverflow.com",
          "relation": "chat"
        }
      ],
      "markdown_extensions": [
        "Prettify"
      ],
      "high_resolution_icon_url": "http://cdn.sstatic.net/stackoverflow/img/apple-touch-icon@2.png"
    },
    {
      "site_type": "main_site",
      "name": "Server Fault",
      "logo_url": "http://cdn.sstatic.net/serverfault/img/logo.png",
      "api_site_parameter": "serverfault",
      "site_url": "http://serverfault.com",
      "audience": "professional system and network administrators",
      "icon_url": "http://cdn.sstatic.net/serverfault/img/apple-touch-icon.png",
      ...
    },
    {
      "site_type": "main_site",
      "name": "Super User",
      ...
    {
      "site_type": "main_site",
      "name": "Meta Stack Overflow",
      ...
    }
    ...
}

I'd like to minimize the number of calls I make to the API and retrieve them all and place them in a data structure in one go for me to be able to make sense of it later on.

I'm trying to adapt one of the lovely solutions I've found here, Making JSON requests within Emacs, to fit what I need to do. It uses the request.el library, from tkf.

The example tkf gave me was able to take the most active question on a site and grab its title and tags properties using json-read, which essentially turns an object into an a-list. This attempt is based off of that solution:

(request
 "https://api.stackexchange.com/2.1/sites"
 :parser 'buffer-string
 :success (function*
       (lambda (&key data &allow-other-keys)
         (let* ((items    (assoc-default 'items data))
            (names    (mapcar (lambda (item) (assoc-default 'name        item)) items))
            (launches (mapcar (lambda (item) (assoc-default 'launch-date item)) items)))
           (mapcar* (lambda (name launch)
              (message "name:`%s` launch:`%s`" name launch))
            names
            launches)))))

...but seems entirely ineffective. The other examples work perfectly, so it is something wrong with my usage.

request.el can be downloaded from the MELPA package repository and, to my knowledge, requires curl to run correctly (which I have).

I suspect the problem lies in my usage (or preparation thereof) of mapcar*, where the following does work as expected:

(mapcar* (lambda (a b) (insert a) (insert b)) '(1 2 3) '(4 5 6))

I know this post is long, but I tried to provide as much information as I could.


Solution

  • You were almost there. This one works for me:

    (request
     "https://api.stackexchange.com/2.1/sites"
     :parser 'json-read
     :success (function*
           (lambda (&key data &allow-other-keys)
             (let* ((items    (assoc-default 'items data))
                (names    (mapcar (lambda (item) (assoc-default 'name        item)) items))
                (launches (mapcar (lambda (item) (assoc-default 'launch_date item)) items)))
               (mapcar* (lambda (name launch)
                  (message "name:`%s` launch:`%s`" name launch))
                names
                launches)))))
    

    Two changes: 1. use json-read instead of buffer-string for parser argument. 2. use launch_date instead of launch-date as the key for alist.