Making a REST request and restructuring the response.
In this example, we are going to make a REST request to the free and public Countries API using the HTTP library that the Stof CLI has access to. We're then going to restructure the response, returning country information for the most dense and least dense countries in Europe.
Stof is sandboxed by default, requiring the host environment to explicitly enable network access. The CLI allows this with the flag "-a http", instructing the document to load the default HTTP library.
Making the HTTP Request
First, we'll need to make an HTTP request to get the data for every European country. Using this endpoint, and passing in "europe" for the region will do the trick.
countries.stof
/// Makes a REST call and parses the response into an object in this document/// at the location "self.countries" or "root.countries"./// Will create the object if needed and drop (delete) it if it already exists.fnpopulateCountries() {// delete the countries object if it already exists and then create a new one drop self.countries; self.countries = new {};// make the HTTP request, parsing the response (based on headers) into our object HTTP.get("https://restcountries.com/v3.1/region/europe", self.countries);// next, we need to restructure each country in the response self.structureCountries();}/// Restructure the countries we receive back from the API call.fnstructureCountries() {// TODO}#[main]fnmain() { self.populateCountries();pln(stringify(self.countries, "toml")); // this will be rather large right now}
Running the Document
Before we move on to restructuring the response, let's use the CLI to test that the HTTP request is working. The TOML output is quite long, so we won't paste it here. However, you will see a lot of data per country and about 53 countries included in the response.
> stof run -a http ./countries.stof
... a bunch of TOML ...
Structuring Each Country
Now that we have an array of countries, let's restructure each one. We want to do the following for each country:
We only want to keep a subset of fields per country: "name", "capital", "altSpellings", "continents", "area", "maps", and "population".
Instead of the name "altSpellings", it is better as "spellings", so rename it.
Ultimately, we want to filter by density, but that information wasn't provided, so create it.
/// Makes a REST call and parses the response into "self.countries"fnpopulateCountries() { drop self.countries; self.countries = new {}; HTTP.get("https://restcountries.com/v3.1/region/europe", self.countries); self.structureCountries();}/// Restructure the countries we receive back from the API call.fnstructureCountries() {let keep =set("name", "capital", "altSpellings", "continents", "area", "maps", "population");let rename =map(("altSpellings", "spellings"));for (country in self.countries.field) {for (field in country.keys()) {if (!keep.contains(field)) {// remove this field from the country (and object if it's an object) country.removeField(field, true); } else {// rename this field if neededif (rename.contains(field)) country.renameField(field, rename.get(field)); } }// add a population density field to this country, defined as population/area country.density =Number.round(country.population / country.area, 2); }}#[main]fnmain() { self.populateCountries();pln(stringify(self.countries, "toml")); // this will still be somewhat long}
Running the Document
Before we move on to getting the most dense and least dense countries, try running the document. You'll still see all of the countries, but they will have the structure we've given them.
> stof run -a http ./countries.stof
... a bunch of TOML ...
Most and Least Dense
Now that we've added a density field to each country, we can look for the most dense and least dense European countries.
We're also creating a new object and combining the two countries' data for the final response.
/// Makes a REST call and parses the response into "self.countries"fnpopulateCountries() { drop self.countries; self.countries = new {}; HTTP.get("https://restcountries.com/v3.1/region/europe", self.countries); self.countries.field =box(self.countries.field); self.structureCountries();}/// Restructure the countries we receive back from the API call.fnstructureCountries() {let keep =set("name", "capital", "altSpellings", "continents", "area", "maps", "population");let rename =map(("altSpellings", "spellings"));for (country in self.countries.field) {for (field in country.keys()) {if (!keep.contains(field)) country.removeField(field, true);elseif (rename.contains(field)) country.renameField(field, rename.get(field)); } country.density =Number.round(country.population / country.area, 2); }}/// Return the densest country.fnmostDenseCountry(): obj {let density =0;let current = null;for (country in self.countries.field) {if (country.density > density) { current = country; density = country.density; } }return current;}/// Return the least dense country.fnleastDenseCountry(): obj {let density = null;let current = null;for (country in self.countries.field) {if (density == null || country.density < density) { current = country; density = country.density; } }return current;}#[main]fnmain() { self.populateCountries();let most = self.mostDenseCountry();let least = self.leastDenseCountry();let together = new { most_dense: most, least_dense: least, };pln(stringify(together, 'toml'));}
Running the Document
Now when we run the document, we'll get the response we expect - both the least dense and most dense European countries, with the fields of our choice.
> stof run -a http ./countries.stof
[least_dense]
area = 61399.0
capital = ["Longyearbyen"]
continents = ["Europe"]
density = 0.04
population = 2562
spellings = ["SJ", "Svalbard and Jan Mayen Islands"]
[least_dense.maps]
googleMaps = "https://goo.gl/maps/L2wyyn3cQ16PzQ5J8"
openStreetMaps = "https://www.openstreetmap.org/relation/1337397"
[least_dense.name]
common = "Svalbard and Jan Mayen"
official = "Svalbard og Jan Mayen"
[least_dense.name.nativeName.nor]
common = "Svalbard og Jan Mayen"
official = "Svalbard og Jan Mayen"
[most_dense]
area = 2.02
capital = ["Monaco"]
continents = ["Europe"]
density = 19427.72
population = 39244
spellings = ["MC", "Principality of Monaco", "Principautรฉ de Monaco"]
[most_dense.maps]
googleMaps = "https://goo.gl/maps/DGpndDot28bYdXYn7"
openStreetMaps = "https://www.openstreetmap.org/relation/1124039"
[most_dense.name]
common = "Monaco"
official = "Principality of Monaco"
[most_dense.name.nativeName.fra]
common = "Monaco"
official = "Principautรฉ de Monaco"
More Concise
The interface we currently have is a more general one, but it could be more concise:
/// Makes a REST call and parses the response into "self.countries"fnpopulateCountries() { drop self.countries; self.countries = new {}; HTTP.get("https://restcountries.com/v3.1/region/europe", self.countries); self.structureCountries();}/// Restructure the countries we receive back from the API call.fnstructureCountries() {let keep =set("name", "capital", "altSpellings", "continents", "area", "maps", "population");let rename =map(("altSpellings", "spellings"));let high_density =0;let high_country = null;let low_density = null;let low_country = null;let density = null;for (country in self.countries.field) {for (field in country.keys()) {if (!keep.contains(field)) country.removeField(field, true);elseif (rename.contains(field)) country.renameField(field, rename.get(field)); } density =Number.round(country.population / country.area, 2); country.density = density;if (density > high_density) { high_country = country; high_density = density; }if (low_density == null || density < low_density) { low_country = country; low_density = density; } } self.countries.densest = high_country; self.countries.least_dense = low_country;}#[main]fnmain() { self.populateCountries();let together = new { most_dense: super.countries.densest, least_dense: super.countries.least_dense, };pln(stringify(together, 'toml'));}
In this version, we're storing two additional references on our "root.countries" object, pointing to the least dense and densest country objects in the response array.