The following ABL code sample retrieves a 5-day weather forecast from the AccuWeather APIs service.
The ABL HTTP client classes are used to retrieve forecast data formatted in JSON from the service.
The JSON returned by the AccuWeather service is not formatted in such a way that the WRITE-JSON() method of the temp-table handle can be used to infer the structure needed to store the data. Specifically, the JSON consists of a simple string containing two top-level objects that are not elements of an array. In this case, the Progress.Json.ObjectModel classes may be used to unpack the JsonObject returned by the HTTP client, and store the data in the individual temp-table fields.
In order to use this technique, the structure of the JSON returned from a particular service must be known in advance, and the temp-table schema must be defined or created in the ABL code rather than inferred as with WRITE-JSON().
&GLOBAL-DEFINE FORECAST_DAYS 5
&GLOBAL-DEFINE FORECAST_URL http://dataservice.accuweather.com/forecasts/v1/daily/5day/01730?apikey=<your API key here>
USING Progress.Lang.Object.
USING OpenEdge.Net.HTTP.RequestBuilder.
USING OpenEdge.Net.HTTP.IHttpRequest.
USING OpenEdge.Net.HTTP.ClientBuilder.
USING OpenEdge.Net.HTTP.IHttpResponse.
USING Progress.Json.ObjectModel.*.
/* HTTP client objects */
DEFINE VARIABLE oRequest AS IHttpRequest NO-UNDO.
DEFINE VARIABLE oResponse AS IHttpResponse NO-UNDO.
DEFINE VARIABLE oEntity AS Object NO-UNDO.
/* JSON objects for manipulating values. Objects can be reused for values
that are not nested. For nested objects, each level must be stored in
its own object. Objects and arrays are different types (JsonObject and
JsonArray respectively. Note that the structure of the JSON must be
known in advance to work with JSON when it is not in a format that the
ABL can infer (such as the output of a previous WRITE-JSON() method).
*/
/* Top level JSON object */
DEFINE VARIABLE oForecast AS JsonObject NO-UNDO.
/* Lower level JSON objects can be reused at the same level. How many are
needed (that is, how deep is the nesting) must be known in advance.
*/
DEFINE VARIABLE oNestObject1 AS JsonObject NO-UNDO.
DEFINE VARIABLE oNestObject2 AS JsonObject NO-UNDO.
DEFINE VARIABLE oNestObject3 AS JsonObject NO-UNDO.
DEFINE VARIABLE oNestArray1 AS JsonArray NO-UNDO.
DEFINE VARIABLE oNestArray2 AS JsonArray NO-UNDO.
/* Variables for iterating through JsonArrays */
DEFINE VARIABLE i AS INTEGER NO-UNDO.
DEFINE VARIABLE j AS INTEGER NO-UNDO.
DEFINE VARIABLE iLength AS INTEGER NO-UNDO.
DEFINE VARIABLE cList AS CHARACTER NO-UNDO.
/* If the JSON is not in a format similar to what is produced by the ABL
WRITE-JSON() method, the temp-table schema cannot be inferred by
READ-JSON(). Instead, the structure of the JSON must be
known in advance and the temp-table must be defined or created dynamically
with a schema that can store it.
*/
DEFINE TEMP-TABLE ttForecast NO-UNDO
FIELD EffectiveDate AS DATETIME
FIELD EffectiveEpochDate AS INTEGER FORMAT '9999999999'
FIELD Severity AS INTEGER
FIELD ForecastText AS CHARACTER FORMAT 'X(30)'
FIELD Category AS CHARACTER FORMAT 'X(30)'
FIELD EndDate AS DATETIME
FIELD EndEpochDate AS INTEGER FORMAT '9999999999'
FIELD MobileLink AS CHARACTER FORMAT 'X(78)'
FIELD Link AS CHARACTER FORMAT 'X(78)'
FIELD DailyForecastDate AS DATETIME EXTENT {&FORECAST_DAYS}
FIELD DailyForecastEpochDate AS INTEGER EXTENT {&FORECAST_DAYS} FORMAT '9999999999'
FIELD DailyForecastTempMinValue AS DECIMAL EXTENT {&FORECAST_DAYS}
FIELD DailyForecastTempMinUnit AS CHARACTER EXTENT {&FORECAST_DAYS}
FIELD DailyForecastTempMinUnitType AS INTEGER EXTENT {&FORECAST_DAYS}
FIELD DailyForecastTempMaxValue AS DECIMAL EXTENT {&FORECAST_DAYS}
FIELD DailyForecastTempMaxUnit AS CHARACTER EXTENT {&FORECAST_DAYS}
FIELD DailyForecastTempMaxUnitType AS INTEGER EXTENT {&FORECAST_DAYS}
FIELD DailyForecastDayIcon AS INTEGER EXTENT {&FORECAST_DAYS}
FIELD DailyForecastDayIconPhrase AS CHARACTER EXTENT {&FORECAST_DAYS} FORMAT 'X(30)'
FIELD DailyForecastNightIcon AS INTEGER EXTENT {&FORECAST_DAYS}
FIELD DailyForecastNightIconPhrase AS CHARACTER EXTENT {&FORECAST_DAYS} FORMAT 'X(30)'
FIELD DailyForecastSources AS CHARACTER EXTENT {&FORECAST_DAYS} FORMAT 'X(78)'
FIELD DailyForecastMobileLink AS CHARACTER EXTENT {&FORECAST_DAYS} FORMAT 'X(78)'
FIELD DailyForecastLink AS CHARACTER EXTENT {&FORECAST_DAYS} FORMAT 'X(78)'
.
/* Make request to service */
oRequest = RequestBuilder:Get("{&FORECAST_URL}")
:Request.
oResponse = ClientBuilder:Build():Client:EXECUTE(oRequest).
IF oResponse:StatusCode <> 200
THEN DO:
MESSAGE
'ERROR: Cannot retrieve forecast from service'
SKIP 'HTTP Status Code:' oResponse:StatusCode
SKIP oResponse:StatusReason
VIEW-AS ALERT-BOX.
RETURN ERROR.
END.
/* Get payload and verify that it is JSON */
oEntity = oResponse:Entity.
IF TYPE-OF(oEntity, JsonObject)
THEN oForecast = CAST(oEntity, JsonObject).
ELSE DO:
MESSAGE
'ERROR: Cannot understand response from service'
VIEW-AS ALERT-BOX.
RETURN ERROR.
END.
/* Create the temp-table record to store the Json content. */
EMPTY TEMP-TABLE ttForecast.
CREATE ttForecast.
/* Store the Headline object in a separate JsonObject, so the simple values it
contains can be retrieved individually.
*/
oNestObject1 = oForecast:GetJsonObject('Headline').
ASSIGN
ttForecast.EffectiveDate = oNestObject1:GetDatetime('EffectiveDate')
ttForecast.EffectiveEpochDate = oNestObject1:GetInteger('EffectiveEpochDate')
ttForecast.Severity = oNestObject1:GetInteger('Severity')
ttForecast.ForecastText = oNestObject1:GetJsonText('Text')
ttForecast.Category = oNestObject1:GetJsonText('Category')
ttForecast.EndDate = oNestObject1:GetDatetime('EndDate')
ttForecast.EndEpochDate = oNestObject1:GetInteger('EndEpochDate')
ttForecast.MobileLink = oNestObject1:GetJsonText('MobileLink')
ttForecast.Link = oNestObject1:GetJsonText('Link')
.
/* Store the DailyForecasts array in a JsonArray. Iterate through the array,
reusing the oNestObject1 variable to store each top-level object. Note
that the array length could be obtained dynamically with the
JsonArray:Length property, but there is no need here; the temp-table where
we are storing the values cannot contain indeterminate arrays, so it is
necessary to know in advance how many forecast days there will be.
*/
oNestArray1 = oForecast:GetJsonArray('DailyForecasts').
DO i = 1 TO {&FORECAST_DAYS}:
oNestObject1 = oNestArray1:getJsonObject(i).
ASSIGN
ttForecast.DailyForecastDate[i] = oNestObject1:GetDatetime('Date')
ttForecast.DailyForecastEpochDate[i] = oNestObject1:GetInteger('EpochDate')
.
/* Store the temperatures in a second JsonObject, because the first one
is still being used for the entire daily forecast.
*/
oNestObject2 = oNestObject1:GetJsonObject('Temperature').
/* Store the Minimum temperature in a third JsonObject, because the
second one is still being used for the entire temperature set.
*/
oNestObject3 = oNestObject2:GetJsonObject('Minimum').
ASSIGN
ttForecast.DailyForecastTempMinValue[i] = oNestObject3:GetDecimal('Value')
ttForecast.DailyForecastTempMinUnit[i] = oNestObject3:GetJsonText('Unit')
ttForecast.DailyForecastTempMinUnitType[i] = oNestObject3:GetInteger('UnitType')
.
/* Min temp is now stored in the temp-table, so the oNestObject3
variable can be reused for the max temp.
*/
oNestObject3 = oNestObject2:GetJsonObject('Maximum').
ASSIGN
ttForecast.DailyForecastTempMaxValue[i] = oNestObject3:GetDecimal('Value')
ttForecast.DailyForecastTempMaxUnit[i] = oNestObject3:GetJsonText('Unit')
ttForecast.DailyForecastTempMaxUnitType[i] = oNestObject3:GetInteger('UnitType')
.
/* Done storing temps, so reuse oNestObject2 for Day and Night objects.
*/
oNestObject2 = oNestObject1:GetJsonObject('Day').
ASSIGN
ttForecast.DailyForecastDayIcon[i] = oNestObject2:GetInteger('Icon')
ttForecast.DailyForecastDayIconPhrase[i] = oNestObject2:GetJsonText('IconPhrase')
.
oNestObject2 = oNestObject1:GetJsonObject('Night').
ASSIGN
ttForecast.DailyForecastNightIcon[i] = oNestObject2:GetInteger('Icon')
ttForecast.DailyForecastNightIconPhrase[i] = oNestObject2:GetJsonText('IconPhrase')
.
/* Sources is an array so presumably there can be more than one source,
although the AccuWeather API does not say specifically. Since the
values are short, the number is unknown, and ABL temp-table fields
cannot be nested, a CHARACTER field containing a comma-separated list is
a good implementation for Sources in the temp-table. */
oNestArray2 = oNestObject1:GetJsonArray('Sources').
ASSIGN
iLength = oNestArray2:LENGTH
cList = ''
.
DO j = 1 TO iLength:
IF j > 1
THEN cList = cList + ','.
cList = cList + oNestArray2:GetJsonText(j).
END.
ttForecast.DailyForecastSources[i] = cList.
/* Store the remaining elements of the daily forecast */
ASSIGN
ttForecast.DailyForecastMobileLink[i] = oNestObject1:GetJsonText('MobileLink')
ttForecast.DailyForecastLink[i] = oNestObject1:GetJsonTExt('Link')
.
END.
/* Sample code only: Display the results. */
DISPLAY ttForecast.
RETURN.