I am using the Weather API from US National Weather Service (https://www.weather.gov/documentation/services-web-api), it's a very simple API that even doesn't require authentication. To get the weather of a location, it's a HTTP GET based on a long and lat as https://api.weather.gov/points/39.7456,-97.0892
Calling the https://api.weather.gov/points/39.7456,-97.0892 return a JSON with a "propeties" element, inside the "properties", the "forecast" is the URL (i.e. https://api.weather.gov/gridpoints/TOP/31,80/forecast) to get the actual forecast by period.
1. Getting Response as String
The below example making use of ZnClient to make a HTTP Restful GET and return the response as a ByteString
| response url |
url := 'https://api.weather.gov/gridpoints/TOP/31,80/forecast'.
response := ZnClient new
url: url;
enforceHttpSuccess: true;
accept: ZnMimeType applicationJson;
get.
^ response
2. Getting Response as Structured Objects
First of all, we want to serialize and deserialize between JSON and Smalltalk objects. I found a very good Open Source framework - NeoJSON (https://github.com/svenvc/NeoJSON).
NeoJSON can be loaded by the "Doing" the following ...
Gofer it
smalltalkhubUser: 'SvenVanCaekenberghe' project: 'Neo';
configurationOf: 'NeoJSON';
loadStable.
NeoJSON has a Reader (i.e. NeoJSONReader) that by default, map JSON response to String, Array and Dictionary.
| response url |
url := 'https://api.weather.gov/gridpoints/TOP/31,80/forecast'.
response := ZnClient new
url: url;
enforceHttpSuccess: true;
accept: ZnMimeType applicationJson;
contentReader: [:entity| NeoJSONReader fromString: entity readStream];
get.
^ response
Inspect the execution of above example showing a dictionary of nest array and dictionaries
3. Mapping to a Schema
Here we created a Smalltalk class - ForecastResponse that has three instance variables; geometry, properties and type. NeoJSONReader create and populate the Schema object using reflection.url := 'https://api.weather.gov/gridpoints/TOP/31,80/forecast'.
response := ZnClient new
url: url;
enforceHttpSuccess: true;
accept: ZnMimeType applicationJson;
contentReader: [:entity|
|reader|
reader:=(NeoJSONReader on: entity readStream).
reader mapInstVarsFor: ForecastResponse.
reader nextAs: ForecastResponse.
];
get.
Inspect the execution of above example showing #type is populated with String, #geometry and #properties are nested dictionaries as we didn't provide any specific mapping for them.
4. Mapping to a Nested Schema
The #properties returned above consisting of nested complex structures of key values pair and array of detail forecast info (i.e. #periods), here we created a Smalltalk class - Forecast to represent the #properties JSON object.The Forecast object has the following declaration ...
Object subclass: #Forecast
instanceVariableNames: 'updated units updateTime validTimes elevation periods'
classVariableNames: ''
package: 'Weather-API'
Herewith we specify the mapping for the #properties JSON...
url := 'https://api.weather.gov/gridpoints/TOP/31,80/forecast'.
response := ZnClient new
url: url;
enforceHttpSuccess: true;
accept: ZnMimeType applicationJson;
contentReader: [:entity|
|reader|
reader:=(NeoJSONReader on: entity readStream).
reader for: ForecastResponse do: [ :mapping |
mapping mapInstVar: #type.
mapping mapInstVar: #geometry.
(mapping mapInstVar: #properties) valueSchema: Forecast
].
reader mapInstVarsFor: Forecast.
reader nextAs: ForecastResponse.
];
get.
Inspecting the execution of above example showing the #properties is a Forecast instance, however the #periods is still an array of dictionaries.
5. Mapping to Nested Schema Array
Another Smalltalk Class - ForecastPeriod is declared to represent the forecast details nested as a dictionary in the #periods array.
Object subclass: #ForecastPeriod
instanceVariableNames: 'name icon isDaytime windSpeed startTime temperatureUnit detailedForecast shortForecast temperatureTrend temperature endTime windDirection number'
classVariableNames: ''
package: 'Weather-API'
A nested mapping whereas the whole JSON response is mapped to nested levels of
- ForecastResponse
- Forecast
- ForecastPeriod
url := 'https://api.weather.gov/gridpoints/TOP/31,80/forecast'.
response := ZnClient new
url: url;
enforceHttpSuccess: true;
accept: ZnMimeType applicationJson;
contentReader: [:entity|
|reader|
reader:=(NeoJSONReader on: entity readStream).
reader for: ForecastResponse do: [ :mapping |
mapping mapInstVar: #type.
mapping mapInstVar: #geometry.
(mapping mapInstVar: #properties) valueSchema: #ForecastSchema
].
reader for: #ForecastSchema do: [ :mapping|
mapping subjectClass: Forecast.
mapping mapInstVars.
(mapping mapInstVar: #periods) valueSchema: #ArrayOfForecast.
].
reader for: #ArrayOfForecast customDo: [ :mapping| mapping listOfElementSchema: ForecastPeriod ].
reader mapInstVarsFor: ForecastPeriod.
reader nextAs: ForecastResponse.
];
get.
Inspecting the execution of above example showing the #properties is a Forecast instance with its #periods consisting of an array of ForecastPeriod instances.
https://github.com/kichoi/backto-smalltalk-pharo/tree/master/Weather-API.package




