Star Wars API — Part 1:Nested Hashes, API Data, and a Simple CLI Application

Non-Object Oriented Solutions

Robert Hopkins
6 min readApr 9, 2016

Application Programming Interfaces or API’s is a common way to access the data used within an application. In an object-oriented language like Ruby it is a way to access libraries and classes within an application. You can parse this data and either use or present it in a unique way within your own applications. The complicated part when you are first starting to use API’s is understanding the format of the data you are presented with. If parsed incorrectly, it can be useless and cause endless errors while writing code. In this post we will take a look at the way the data is presented from a couple of API’s and talk about effectively parsing that information to make it useful.

In a Galaxy Far, Far Away….

A great beginner API is the unofficial, open source Star Wars API presented here. This API is a collection of data from the 7 official live-action movies. It includes information about each movie, many of the prominent and background characters, and a large portion of the planets, starships, and species seen throughout the 7 Episodes. (The 6 available data categories are people, planets, films, vehicles, starships and species as you will soon see.)

The data is presented originally as a link, with the top level pointing you to the 6 categories of data. In order to reach the hash, from your application you need to reach out and grab the .json file from the url. With the .json gem and Rest Client gem you can convert a URL into a workable nested hash.

require 'RestClient'
require 'JSON'
url = "http://swapi.co/api/"def import_data(url)
string = RestClient.get(url)
hash = JSON.parse(string)
end
#RestClient grabs the url and returns a string
#JSON will parse this string into a hash
#import_data will return the imported hash
import_data(url)#This is the returned hash:=>{"people"=>"http://swapi.co/api/people/", "planets"=>"http://swapi.co/api/planets/", "films"=>"http://swapi.co/api/films/", "species"=>"http://swapi.co/api/species/", "vehicles"=>"http://swapi.co/api/vehicles/", "starships"=>"http://swapi.co/api/starships/"}

This may not look useful, but it represents “pointers to 6 chapters of an encyclopedia”. If you wanted to reach into them you could use an IF statement to ask the user which chapter they want to search, allowing you to write individual methods for each.

Let’s say we wanted to look into the films pages and return the names of all characters in a movie when given an episode number.

If you only wanted to concern yourself with this section of the API to start, you can reach into the films section when you do your first #import_data. In order to do that, open the SWAPI and you will see the new URL you need to import from is now “http://swapi.co/api/films/”. Now when you run the #import_data method with this new url, you get the following hash: (Only the first episode is presented in the block below, the actual hash is significantly longer.)

{
"count": 7,
"next": null,
"previous": null,
"results": [
{
"title": "A New Hope",
"episode_id": 4,
"opening_crawl": "It is a period of civil war.\r\nRebel spaceships, striking\r\nfrom a hidden base, have won\r\ntheir first victory against\r\nthe evil Galactic Empire.\r\n\r\nDuring the battle, Rebel\r\nspies managed to steal secret\r\nplans to the Empire's\r\nultimate weapon, the DEATH\r\nSTAR, an armored space\r\nstation with enough power\r\nto destroy an entire planet.\r\n\r\nPursued by the Empire's\r\nsinister agents, Princess\r\nLeia races home aboard her\r\nstarship, custodian of the\r\nstolen plans that can save her\r\npeople and restore\r\nfreedom to the galaxy....",
"director": "George Lucas",
"producer": "Gary Kurtz, Rick McCallum",
"release_date": "1977-05-25",
"characters": [
"http://swapi.co/api/people/1/",
"http://swapi.co/api/people/2/",
"http://swapi.co/api/people/3/",
"http://swapi.co/api/people/4/",
"http://swapi.co/api/people/5/",
"http://swapi.co/api/people/6/",
"http://swapi.co/api/people/7/",
"http://swapi.co/api/people/8/",
"http://swapi.co/api/people/9/",
"http://swapi.co/api/people/10/",
"http://swapi.co/api/people/12/",
"http://swapi.co/api/people/13/",
"http://swapi.co/api/people/14/",
"http://swapi.co/api/people/15/",
"http://swapi.co/api/people/16/",
"http://swapi.co/api/people/18/",
"http://swapi.co/api/people/19/",
"http://swapi.co/api/people/81/"
],
"planets": [
"http://swapi.co/api/planets/2/",
"http://swapi.co/api/planets/3/",
"http://swapi.co/api/planets/1/"
],
"starships": [
"http://swapi.co/api/starships/2/",
"http://swapi.co/api/starships/3/",
"http://swapi.co/api/starships/5/",
"http://swapi.co/api/starships/9/",
"http://swapi.co/api/starships/10/",
"http://swapi.co/api/starships/11/",
"http://swapi.co/api/starships/12/",
"http://swapi.co/api/starships/13/"
],
"vehicles": [
"http://swapi.co/api/vehicles/4/",
"http://swapi.co/api/vehicles/6/",
"http://swapi.co/api/vehicles/7/",
"http://swapi.co/api/vehicles/8/"
],
"species": [
"http://swapi.co/api/species/4/",
"http://swapi.co/api/species/5/",
"http://swapi.co/api/species/3/",
"http://swapi.co/api/species/2/",
"http://swapi.co/api/species/1/"
],
"created": "2014-12-10T14:23:31.880000Z",
"edited": "2015-04-11T09:46:52.774897Z",
"url": "http://swapi.co/api/films/1/"
},

If this hash was saved as a variable and you called the #keys method on it, it would only return 4 keys: count, next, previous , results. Count is the total number of entries, next/previous are pointers to the “next/previous page of the encyclopedia.” The films is a simple case because all the entries are presented on one page.

import_data(url).keys #=> ["count", "next", "previous", "results"]

To access the useful information you need to begin reaching into this nested hash. The value of the results key is where all the pertinent information is. To reach this second level of this hash you have to call upon the “results” key and parse the value for film data:

hash = import_data(url) #the above hash is saved to this variable hash["results"]  #=> returns a giant array of the results

We can now begin to iterate over this data and start to store and eventually return values we are looking for.

Data: Iteration, Storing, and Parsing

Lets start by seeing what we get when we iterate over this array.

hash["results"].each do |film|   #film is each element in the array
puts film #This will puts each film's data
end

Since we are iterating over an array, only one value is needed between the pipes(||). Referencing back to the hash above you will see that each element is itself a hash with several key, value pairs. The key is the type of data and the values are the actual data (or a link to where that data is stored).

For our application we are given an episode number and we are looking to output the names of all the characters in the movie so we are only concerned of returning the values in the characters array if the episode number matches our user’s inputted value.

ep_num = gets.chomp            #must be a integer 1-7, lets choose 4
def get_char_list_from_film(ep_num)
char_urls = []
url = "http://www.swapi.co/api/films/"
films_hash = import_data(url) #same method as before
films_hash["results"].each do |films| #iterate over the array
if films["episode_id"] == ep_num #check if right movie
char_urls = chars["characters"] #store array of urls
end
end
char_urls #return array of urls
end

char_urls = [
"http://swapi.co/api/people/1/",
"http://swapi.co/api/people/2/",
"http://swapi.co/api/people/3/",
"http://swapi.co/api/people/4/",
"http://swapi.co/api/people/5/",
"http://swapi.co/api/people/6/",
"http://swapi.co/api/people/7/",
"http://swapi.co/api/people/8/",
"http://swapi.co/api/people/9/",
"http://swapi.co/api/people/10/",
"http://swapi.co/api/people/12/",
"http://swapi.co/api/people/13/",
"http://swapi.co/api/people/14/",
"http://swapi.co/api/people/15/",
"http://swapi.co/api/people/16/",
"http://swapi.co/api/people/18/",
"http://swapi.co/api/people/19/",
"http://swapi.co/api/people/81/"
]

We’re almost there! Now we have an array of URL’s that contains the data of all the characters in Episode 4. Let’s go to one of these URL’s in a browser and see what this data looks like. Click the link below.

Person #1 is Luke Skywalker:


{
"name": "Luke Skywalker",
"height": "172",
"mass": "77",
"hair_color": "blond",
"skin_color": "fair",
"eye_color": "blue",
"birth_year": "19BBY",
"gender": "male",
"homeworld": "http://swapi.co/api/planets/1/",
"films": [
"http://swapi.co/api/films/6/",
"http://swapi.co/api/films/3/",
"http://swapi.co/api/films/2/",
"http://swapi.co/api/films/1/",
"http://swapi.co/api/films/7/"
],
"species": [
"http://swapi.co/api/species/1/"
],
"vehicles": [
"http://swapi.co/api/vehicles/14/",
"http://swapi.co/api/vehicles/30/"
],
"starships": [
"http://swapi.co/api/starships/12/",
"http://swapi.co/api/starships/22/"
],
"created": "2014-12-09T13:50:51.644000Z",
"edited": "2014-12-20T21:17:56.891000Z",
"url": "http://swapi.co/api/people/1/"
}

Now we see this is a hash, but like the URL’s we have been using, they need to be pulled via RestClient and then parsed via JSON. Then once this hash is stored to a variable we need to iterate over it and store the value of the “name” key, since “name” => “Luke Skywalker” and repeat this process for every URL so we can store the name of every character.

We should create a method to parse this array of URL’s:

def parse_char_data(char_urls)
char_hash_arr = [] #empty array to store output
char_urls.each do |url| # of parsing URLs
name = RestClient.get(url)
char_hash_arr << JSON.parse(name) #store each character hash
end #in 1 large array to
name_arr = [] #iterate over in the next step
char_hash_arr.each do |char|
name_arr << char["name"] #shovel each name into array
end
name_arr #return the array of names
end

Now this array (name_arr) should look like:

#=> ["Luke Skywalker", "C-3PO", "R2-D2", "Darth Vader", "Leia Organa", "Owen Lars","Beru Whitesun lars","R5-D4","Biggs Darklighter","Obi-Wan Kenobi","Wilhuff Tarkin","Chewbacca","Han Solo","Greedo","Jabba Desilijic Tiure","Wedge Antilles","Jek Tono Porkins","Raymus Antilles"]

Great! Now we just need to print that to our terminal. The best way to combine these 2 above methods is to call them together in a separate method. Here is one way to do that:

####DONT FORGET TO CAPTURE USER INPUT FOR THE VARIABLE ep_num ####def show_list_of_chars_in_ep(ep_num)
char_arr = get_char_list_from_film(ep_num)
puts parse_char_data(char_arr)
end

The first method returns an array (of URLs) which is stored to char_arr
The second method uses this array of URLs to also returns an array (of names) and is "puts" to the terminal

We place “puts” before the second method to instruct the application to print out the values inside the array to the terminal.

When all is said and done, you should have something like this in your terminal:

My file is compiled at the location: bin/run.rb. This version also can take an argument of a character name and will return the titles of all the films the character was in. How would go about adding in the option to sift through multiple branches of this data?

Follow this link for my github repository on this subject.

- Robert Hopkins [robert.hopkins@flatironschool.com]

--

--