Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
xczar0
Explorer
11,659
2018-03-17 - Applied editorial comments related to language correctness

Hi!

With this blog post I wanted to describe and explain on consuming external APIs returning JSONs via HTTP protocol.

This blog post is dedicated mostly for people who never worked with JSON before or need more explanation.

This one will be a long one - I'm already sorry for that 🙂

What's JSON?

JSON stands for JavaScript Object Notation - it allows us to represent state of objects and send it via stateless protocol such as HTTP and then retrieve data carried over by deparsing it in literally any programming language.

Advantage of such approach is ability to make our programs fetch data outside of server sandbox without need to establish some "hardwired" connections.

Why you should consider moving from XML to JSON standard?

XML is a markup language (eXtensible Markup Language). It enables us to transfer documents very efficiently (text data and its formatting) via many protocols and keep document shape the same all the way.

BUT there's a downside of XML - huge amount of markups that are transferred together with data we really need.

Here's an XML example:
<Document>
<Name>
Document_Name
</Name>
<Content>
Document_content_here
</Content>
</Document>

And here's its JSON counterpart:
{
"Name" : "Document_Name",
"Content" : "Document_content_here"
}

Even with such simple example we can see that JSON is clearly shorter.That's because JSONs are constructed as key-value pairs.

Shorter content sent via the internet = faster download.

No additional ending markers are being transferred with JSON.

If you want to know more about JSONs and their capabilities - please visit JSON tutorial by W3Schools.

Checking on what's returned by external API.

Sometimes you might encounter a situation where documentation of API is not that well described and you do not know what's being returned after your call.

The most hardcore way of "decoding" JSON for the first time would be standard Eclipse ADT (or even SE80) debugger and constructing structures for parser from plain text.

But I'm lazy and I decided to use external tool called Postman.

I also used HTTP Requester (addon for Firefox, but Postman seems to have less bugs and it comes as a separate application.

Here's an exemplary screen of Postman:



We can treat Postman as a "testbench" for our silly acitivities and a major helper in creating structures for JSON parser.

Exemplary application

Let's imagine situation where we develop application which is responsible for fetching weather data from source on the internet (I picked OpenWeather API - you  have to register first to get your own auth token - it's free). Warning - please wait up to 1 hour before testing, as API needs time to synchronize and activate token keys.

How can we use weather data?

We could get location information of sourcing manager phone and put weather information on his application directly, we could add it to Fiori launchpad to make it neater and more welcoming or we could show him weather conditions at place where he/she's heading on next business trip at the moment of logon to our SAP system.

Disclaimer:

I will not build fully working application - my intention is to show you how easy it is to get data via HTTP, deparse JSON data and utilize it accoring to your needs.

Another thing is - I'm aware that OpenWeather API have multiple API calls returning multiple types of JSONs, yet i limited this tutorial to just one.

Interface

First part will be the interface of our data carrier.

I named it ZIF_CK_DATA_CARRIER - of course in production code this name shall not remain.

The most imporant part of our interface will be... type! Why you might ask?

That's because i want to make sure that all classes which are supposed to deal with "raw" JSON sent from OpenWeather and implement this interface "have the knowledge" of returned type by the API.

Why our type looks so weird and complicated?

It's simple - if I want parser to work as expected I need to make sure that ABAP structure is 100% same as JSON one sent to us. That's the way parser work - we need mirroring structures/object on two sides.

Of course - if I had multiple different API calls, i would name interface properly - here i would propose ZIF_OWEATHER_CURRWEATH_BY_CITY - purely to differentiate what this interface does and where it fetches data from.
interface ZIF_CK_DATA_CARRIER
public .
types: "! Coordinates of weather station
begin of coord,
lon type p length 5 decimals 2,
lat type p length 5 decimals 2,
end of coord,
"! Weather conditions
begin of weather_line,
id type i,
main type string,
description type string,
icon type char10,
end of weather_line,
tt_weather type standard table of weather_line with default key,
begin of request_line,
coord type coord,
weather type tt_weather,
base type string,
"! Main weather data
begin of main,
temp type p length 5 decimals 2,
pressure type i,
humidity type i,
temp_min type p length 5 decimals 2,
temp_max type p length 5 decimals 2,
end of main,
"! Visibility in meters
visibility type i,
"! Wind conditions
begin of wind,
speed type p length 5 decimals 2,
deg type i,
end of wind,
"! Clouds state (percentage)
begin of clouds,
all type i,
end of clouds,
"! Unix datetime of measurement
dt type string,
"! Some system messages and sunrise and sunset information
begin of sys,
type type i,
id type i,
message type p length 5 decimals 4,
country type char10,
sunrise type i,
sunset type i,
end of sys,
"! Unique ID of the city/station
id type string,
"! City/station name
name type string,
"! HTTP code returned
cod type i,
end of request_line.
methods: get_data returning value(r_data_table) type request_line,
set_data importing i_value type string.
endinterface.

As you can see our type consist of basic data types, structures and one table. I know I could've created some data type for all these "p length 5 decimals 2", but I wanted to keep it clear for you and enable it to be copied directly and tried out without having to create something in se11/Eclipse data element.

Interface has two methods - get and set data. Get data should allow us to return data outside of the object and set_data is meant to parse or pass data to object of a class which is implementing this interface.

HTTP client class

Below we have a class implementing aforementioned interface: ZCL_CK_HTTP_CLIENT.

Here when we initialize the object we automatically fetch the data and we parse it.To send and receive request results i used standard ABAP class CL_HTTP_CLIENT

Of course if you wish to change the way it's being initialized and change the moment of sending the request - refactoring is up to you.

I added method to return city name and country name if there's a need to manage multiple locations at the same fetch process.

OpenWeather accepts get and sends authorization token directly in the request url, so we do not have to perform any additional steps, such as filling header, auth-key et cetera before sending.

 
class zcl_ck_http_client definition
public
final
create public .

public section.
interfaces: zif_ck_data_carrier.
types: begin of location,
country type char03,
city type char40,
end of location.
methods:
"! <p class="shorttext synchronized" lang="en"></p>
"! Create object, send request and parse JSON data
"! @parameter i_location | <p class="shorttext synchronized" lang="en"></p>
"! @parameter i_auth_key | <p class="shorttext synchronized" lang="en"></p>
constructor importing i_location type location i_auth_key type string,
"! <p class="shorttext synchronized" lang="en"></p>
"! Returns cityname
"! @parameter r_city | <p class="shorttext synchronized" lang="en"></p>
get_city returning value(r_city) type location-city,
"! <p class="shorttext synchronized" lang="en"></p>
"! Returns country name
"! @parameter r_country | <p class="shorttext synchronized" lang="en"></p>
get_country returning value(r_country) type location-country,
"! <p class="shorttext synchronized" lang="en"></p>
"! Returns http_client.
"! @parameter r_http_client | <p class="shorttext synchronized" lang="en"></p>
get_http_client returning value(r_http_client) type ref to if_http_client.
protected section.
private section.
data: request_line type zif_ck_data_carrier~request_line,
city type location-city,
country type location-country,
http_client type ref to if_http_client.
methods: "! <p class="shorttext synchronized" lang="en"></p>
"! Construct url address for HTTP Client
"! @parameter i_country | Country for which data is fetched <p class="shorttext synchronized" lang="en"></p>
"! @parameter i_city | City for which data is fethced <p class="shorttext synchronized" lang="en"></p>
"! @parameter i_auth_key | Unique authorization key for user <p class="shorttext synchronized" lang="en"></p>
"! @parameter r_url_address | <p class="shorttext synchronized" lang="en"></p>
get_url_address importing i_country type location-country
i_city type location-city
i_auth_key type string
returning value(r_url_address) type string.
endclass.



class zcl_ck_http_client implementation.
method zif_ck_data_carrier~get_data.
r_data_table = request_line.
endmethod.


method get_url_address.
r_url_address = |http://api.openweathermap.org/data/2.5/weather?q={ i_city },{ i_country }&appid={ i_auth_key }|.

endmethod.


method constructor.
cl_http_client=>create_by_url( exporting url = get_url_address( i_country = i_location-country
i_city = i_location-city
i_auth_key = i_auth_key ) " URL
importing client = http_client " HTTP Client Abstraction
exceptions argument_not_found = 1
plugin_not_active = 2
internal_error = 3
others = 4 ).
if sy-subrc <> 0.
raise exception type cx_sy_ref_creation.
endif.
http_client->send( ).
http_client->receive( ).
zif_ck_data_carrier~set_data( i_value = http_client->response->get_cdata( ) ).
city = i_location-city.
country = i_location-country.
endmethod.

method get_city.
r_city = city.
endmethod.

method get_country.
r_country = country.
endmethod.

method get_http_client.
r_http_client = http_client.
endmethod.

method zif_ck_data_carrier~set_data.
/ui2/cl_json=>deserialize( EXPORTING json = i_value pretty_name = /ui2/cl_json=>pretty_mode-low_case CHANGING data = request_line ).
endmethod.


endclass.

As I do not know yet where my class is going to be used, I created a test class, just to check if everything is fine (don't forget to put your own AuthKey over there).

And yes... I'm aware that it's not 100% correct approach to a test class, but I believe it serves its purpose well to show our examplary call is working and gets the data.
*"* use this source file for your ABAP unit test classes
class ltcl_object_creation definition final for testing
duration short
risk level harmless.

private section.
methods:
http_object_creator for testing raising cx_static_check.
endclass.


class ltcl_object_creation implementation.

method http_object_creator.
cl_abap_unit_assert=>assert_not_initial( exporting act = new zcl_ck_http_client( i_location = value #( city = 'London' country = 'UK' )
i_auth_key = 'YourAuthKeyHere' )->zif_ck_data_carrier~get_data( ) ).

endmethod.

endclass.

To test it properly, please use Eclipse IDE and put it to "Test classes" tab of main class editor window:



That's it for today - i hope you like it and this blog post will be useful for you at some point.

As always - let me know what do you think - was it helpful?

Cheers,
Cezary

 
6 Comments
Labels in this area