Monday, 21 October 2019

Smalltalking to RESTful Service (NeoJSON)


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
Array of dictionaries is mapped to #listOfElementSchema by the Reader's #for:customDo: message.


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.



Note: Source available on ...
https://github.com/kichoi/backto-smalltalk-pharo/tree/master/Weather-API.package

Thursday, 17 October 2019

Back to Smalltalk (1981 - 2019)


My first awareness with Smalltalk was through the Byte Magazine 1981-08 when I was still in school.  Since then, I was lucky enough to have the opportunities working on various Smalltalk platforms from OTI, Digitalk and IBM.  Since 1995, Java took over the throne of Object Oriented Programming Language and I shifted gear to Java/JEE.  Almost 25 years passed, I used numerous languages; Java, Javascript, C, C++, Objective C, Salesforce Apex, Perl and Go.  In my opinion, NONE of them surpass the elegance of Smalltalk.  I was working when I was coding with other languages, but I was enjoying while I was expressing my thoughts using Smalltalk (please note that it is expressing thought rather than coding some logic).

So, after 25 years; I am back to Smalltalk.  Not sure any people still care about Smalltalk now, I guess I just want to come back and enjoy programming and share my back to Smalltalk journey.  As at now, at the very beginning of my return journey, I haven't commit to a specific Smalltalk tool yet - some of the popular one ...

As I plowing through the journey, guess I will have the opportunity to try them individually.  I first started with VW Smalltalk with its Personal Use License.  Unfortunately, the free Personal Use License doesn't support HTTPS networking.  In today's on the cloud for everything, not HTTPS networking is a show stopper (or I shall be blamed of too cheap for not willing to pay the license fee for https).  So for now, I started with Pharo!