Introduction:
This blog post is the continuation of my previous blog post “ABAP to JSON” conversion. In this post, we will develop a class that will convert the JSON data to ABAP. This will be helpful in cases where the class “/UI2/CL_JSON” is not available. If you have this standard class available, then feel free to leave this blog post here.
Also, this blog post will be helpful if someone tries to understand on the available option to convert JSON to ABAP. As this post is based on custom class development for the conversion, it can give ideas on all the steps been involved in the process.
Problem Statement:
As mentioned already, “/UI2/CL_JSON” class is really handy and probably the best way to deal with “ABAP to JSON” conversion and vice-versa. But, recently I came across a requirement to convert data between ABAP and JSON, and the class “/UI2/CL_JSON” was not present in the system due to its current patch level. So, I came up with the idea to create a “custom class” for JSON to ABAP conversion and thought to share it so that it will be helpful for someone.
Goal:
The goal of this blog post is to convert the JSON data to ABAP using a custom class which in turn uses the standard XML transformation. By end of this blog post, we will be able to convert the below data to JSON. Sample JSON Payload can also be found out from here, which I found from the OData reference site(click here).
{"@odata.context":"https://services.odata.org/TripPinRESTierService/(S(5d0nygngig13surqamierrei))/$metadata#People","value":[{"UserName":"russellwhyte","FirstName":"Russell","LastName":"Whyte","MiddleName":null,"Gender":"Male","Age":null,"Emails":["Russell@example.com","Russell@contoso.com"],"FavoriteFeature":"Feature1","Features":["Feature1","Feature2"],"AddressInfo":[{"Address":"187 Suffolk Ln.","City":{"Name":"Boise","CountryRegion":"United States","Region":"ID"}}],"HomeAddress":null},{"UserName":"scottketchum","FirstName":"Scott","LastName":"Ketchum","MiddleName":null,"Gender":"Male","Age":null,"Emails":["Scott@example.com"],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[{"Address":"2817 Milton Dr.","City":{"Name":"Albuquerque","CountryRegion":"United States","Region":"NM"}}],"HomeAddress":null},{"UserName":"ronaldmundy","FirstName":"Ronald","LastName":"Mundy","MiddleName":null,"Gender":"Male","Age":null,"Emails":["Ronald@example.com","Ronald@contoso.com"],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[{"Address":"187 Suffolk Ln.","City":{"Name":"Boise","CountryRegion":"United States","Region":"ID"}}],"HomeAddress":null},{"UserName":"javieralfred","FirstName":"Javier","LastName":"Alfred","MiddleName":null,"Gender":"Male","Age":null,"Emails":["Javier@example.com","Javier@contoso.com"],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[{"Address":"89 Jefferson Way Suite 2","City":{"Name":"Portland","CountryRegion":"United States","Region":"WA"}}],"HomeAddress":null},{"UserName":"willieashmore","FirstName":"Willie","LastName":"Ashmore","MiddleName":null,"Gender":"Male","Age":null,"Emails":[],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[],"HomeAddress":null},{"UserName":"vincentcalabrese","FirstName":"Vincent","LastName":"Calabrese","MiddleName":null,"Gender":"Male","Age":null,"Emails":["Vincent@example.com","Vincent@contoso.com"],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[{"Address":"55 Grizzly Peak Rd.","City":{"Name":"Butte","CountryRegion":"United States","Region":"MT"}}],"HomeAddress":null},{"UserName":"clydeguess","FirstName":"Clyde","LastName":"Guess","MiddleName":null,"Gender":"Male","Age":null,"Emails":[],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[],"HomeAddress":{"Address":null,"City":null}},{"UserName":"keithpinckney","FirstName":"Keith","LastName":"Pinckney","MiddleName":null,"Gender":"Male","Age":null,"Emails":["Keith@example.com","Keith@contoso.com"],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[{"Address":"55 Grizzly Peak Rd.","City":{"Name":"Butte","CountryRegion":"United States","Region":"MT"}}],"HomeAddress":null},{"UserName":"marshallgaray","FirstName":"Marshall","LastName":"Garay","MiddleName":null,"Gender":"Male","Age":null,"Emails":["Marshall@example.com","Marshall@contoso.com"],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[{"Address":"55 Grizzly Peak Rd.","City":{"Name":"Butte","CountryRegion":"United States","Region":"MT"}}],"HomeAddress":null},{"UserName":"ryantheriault","FirstName":"Ryan","LastName":"Theriault","MiddleName":null,"Gender":"Male","Age":null,"Emails":["Ryan@example.com","Ryan@contoso.com"],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[{"Address":"55 Grizzly Peak Rd.","City":{"Name":"Butte","CountryRegion":"United States","Region":"MT"}}],"HomeAddress":null},{"UserName":"elainestewart","FirstName":"Elaine","LastName":"Stewart","MiddleName":null,"Gender":"Female","Age":null,"Emails":["Elaine@example.com","Elaine@contoso.com"],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[{"Address":"55 Grizzly Peak Rd.","City":{"Name":"Butte","CountryRegion":"United States","Region":"MT"}}],"HomeAddress":null},{"UserName":"salliesampson","FirstName":"Sallie","LastName":"Sampson","MiddleName":null,"Gender":"Female","Age":null,"Emails":["Sallie@example.com","Sallie@contoso.com"],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[{"Address":"87 Polk St. Suite 5","City":{"Name":"San Francisco","CountryRegion":"United States","Region":"CA"}},{"Address":"89 Chiaroscuro Rd.","City":{"Name":"Portland","CountryRegion":"United States","Region":"OR"}}],"HomeAddress":null},{"UserName":"jonirosales","FirstName":"Joni","LastName":"Rosales","MiddleName":null,"Gender":"Female","Age":null,"Emails":["Joni@example.com","Joni@contoso.com"],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[{"Address":"55 Grizzly Peak Rd.","City":{"Name":"Butte","CountryRegion":"United States","Region":"MT"}}],"HomeAddress":null},{"UserName":"georginabarlow","FirstName":"Georgina","LastName":"Barlow","MiddleName":null,"Gender":"Female","Age":null,"Emails":["Georgina@example.com","Georgina@contoso.com"],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[{"Address":"55 Grizzly Peak Rd.","City":{"Name":"Butte","CountryRegion":"United States","Region":"MT"}}],"HomeAddress":null},{"UserName":"angelhuffman","FirstName":"Angel","LastName":"Huffman","MiddleName":null,"Gender":"Female","Age":null,"Emails":["Angel@example.com"],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[{"Address":"55 Grizzly Peak Rd.","City":{"Name":"Butte","CountryRegion":"United States","Region":"MT"}}],"HomeAddress":null},{"UserName":"laurelosborn","FirstName":"Laurel","LastName":"Osborn","MiddleName":null,"Gender":"Female","Age":null,"Emails":["Laurel@example.com","Laurel@contoso.com"],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[{"Address":"87 Polk St. Suite 5","City":{"Name":"San Francisco","CountryRegion":"United States","Region":"CA"}}],"HomeAddress":null},{"UserName":"sandyosborn","FirstName":"Sandy","LastName":"Osborn","MiddleName":null,"Gender":"Female","Age":null,"Emails":["Sandy@example.com","Sandy@contoso.com"],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[{"Address":"87 Polk St. Suite 5","City":{"Name":"San Francisco","CountryRegion":"United States","Region":"CA"}}],"HomeAddress":null},{"UserName":"ursulabright","FirstName":"Ursula","LastName":"Bright","MiddleName":null,"Gender":"Female","Age":null,"Emails":["Ursula@example.com","Ursula@contoso.com"],"FavoriteFeature":"Feature1","Features":[],"AddressInfo":[{"Address":"87 Polk St. Suite 5","City":{"Name":"San Francisco","CountryRegion":"United States","Region":"CA"}}],"HomeAddress":null},{"@odata.type":"#Trippin.Manager","UserName":"genevievereeves","FirstName":"Genevieve","LastName":"Reeves","MiddleName":null,"Gender":"Female","Age":null,"Emails":["Genevieve@example.com","Genevieve@contoso.com"],"FavoriteFeature":"Feature1","Features":[],"Budget":0,"AddressInfo":[{"Address":"87 Polk St. Suite 5","City":{"Name":"San Francisco","CountryRegion":"United States","Region":"CA"}}],"HomeAddress":null,"BossOffice":null},{"@odata.type":"#Trippin.Employee","UserName":"kristakemp","FirstName":"Krista","LastName":"Kemp","MiddleName":null,"Gender":"Female","Age":null,"Emails":["Krista@example.com"],"FavoriteFeature":"Feature1","Features":[],"Cost":0,"AddressInfo":[{"Address":"87 Polk St. Suite 5","City":{"Name":"San Francisco","CountryRegion":"United States","Region":"CA"}}],"HomeAddress":null}]}
So, let’s start!
Step 1: Create a custom class for JSON to ABAP Conversion
Go to t-code SE24 and create a new class “ZCONVERT_JSON_TO_ABAP”. I will provide the entire source code of the class below:
Type Declaration
Attribute Declaration
Method Declaration
Step 2: Method declaration – Constructor
Declare constructor with 2 parameters
- Importing parameter – JSON – The JSON which will be converted to ABAP
- Importing parameter – FIELD_MAPPING – Mapping of field between JSON data and ABAP Structure. It’s only required when JSON attribute name is invalid as per ABAP field naming conversion. Please refer to the Step 4 for more detail
Logic:
- Importing data are saved to the class attribute.
- You can see we have a concatenation statement in line 4 in the below image. It basically adds ‘{ “struct : { ‘ in the beginning of the JSON and ‘}’ in the end. The reason being, we will use the standard transformation to convert JSON to ABAP and it expect “struc” as a main attribute of the JSON. Please refer to the “CALL TRANSFORMATION” code in “CONVERT” Method.
- First JSON converted to xstring format and then the attribute names are capitalized. Please refer to “CAPITALISE_JSON_ATTRIBUTE” for more info
Step 3: Method declaration – CONVERT_STRING_TO_XSTRING
This method has 2 parameters
- Importing parameter – INPUT – JSON in String format
- Returning parameter – OUTPUT – Converted to XSTRING
Step 4: Method declaration – CAPITALISE_JSON_ATTRIBUTES
This method has 2 parameters
- Importing parameter – INPUT – JSON in XString format
- Returning parameter – OUTPUT – Converted to XSTRING
Need of Capitalisation method:
The name during conversion from JSON to ABAP is case-sensitive. The attribute name in the JSON can be in any case, for example, camel case but, in ABAP the name of the field is always in upper case. So, whenever we will run the transformation, there will be no data in the output due to the mismatch of the case.
See, the below example, the field name username is in the camel case. But in the ABAP structure, the field name will be upper case. This method will make the attribute names to upper case so that in output we will see the data
{
"value": {
"UserName": "russellwhyte",
"FirstName": "Russell",
"LastName": "Whyte",
"MiddleName": "",
"Gender": "Male"
}
}
(Original JSON payload)
TYPES:
BEGIN OF user_info,
username TYPE string,
firstname TYPE string,
lastname TYPE string,
middlename TYPE string,
gender TYPE string,
age TYPE string,
emails TYPE emails,
favouritefeature TYPE string,
features TYPE features,
addressinfo TYPE addresses,
END OF user_info,
(ABAP Structure)
{
"VALUE": {
"USERNAME": "russellwhyte",
"FIRSTNAME": "Russell",
"LASTNAME": "Whyte",
"MIDDLENAME": "",
"GENDER": "Male"
}
}
(Output of the method where the name of the attributes of JSON are capitalized)
Another functionality of this method is that doing the mapping between JSON Attribute and ABAP structure when the name of the JSON attribute is illegal in terms of ABAP naming convention.
For example, in the sample file, we have the first attribute as “@odata.context”, which is invalid as per ABAP field naming as “Special chars are not allowed” in name of the field, and the system will go into dump during transformation. Below screenshot for reference:
In that case, we can leverage field_mapping, discussed in the “Constructor” method, and will pass a name that is as per ABAP naming convention.
So, now the system will not go into dump and if the field “ODATA” and “ODATA_TYPE” are available, then it will be filled with the respective value from JSON data. The table “FIELD_MAP” is used inside this method to replace the attribute name with the alternate name. It’s cool, isn’t it?
The source code of the method is a bit long, so I am not providing the same here. Please refer to the end of this blog post for complete source code.
Step 5: Method declaration – CONVERT
This method has only one parameter:
- Exporting parameter – ABAP – Converted data.
Whew! We are done finally.
Now, we will create a report program and convert the JSON to ABAP.
Step 6: Create Report program to convert JSON to ABAP
We will create a report program and convert it to ABAP. But, I will not be consuming the GET service mentioned in the starting of this blog post, as there is some issues with HTTPS connection our system. So, I have saved the JSON payload in a file and used file_upload to get JSON data. Then we will convert the JSON data to ABAP.
You can also consume the GET service(URL is already provided in the TOP of this blog post) and get the JSON and convert it.
Source code of Report Program:
REPORT zjson_2_abap.
*-------------------------------------------------------------------------------------------------------
* Begining of selection screen design
*-------------------------------------------------------------------------------------------------------
PARAMETERS:
path TYPE string LOWER CASE.
*-------------------------------------------------------------------------------------------------------
* End of selection screen design
*-------------------------------------------------------------------------------------------------------
*-------------------------------------------------------------------------------------------------------
* Start of type declaration for JSON document structure
*-------------------------------------------------------------------------------------------------------
TYPES:
BEGIN OF city,
name TYPE string,
countryregion TYPE string,
region TYPE string,
END OF city,
BEGIN OF address,
address TYPE string,
city TYPE city,
END OF address,
emails TYPE TABLE OF string INITIAL SIZE 0 WITH NON-UNIQUE DEFAULT KEY,
addresses TYPE TABLE OF address INITIAL SIZE 0 WITH NON-UNIQUE DEFAULT KEY,
features TYPE TABLE OF string INITIAL SIZE 0 WITH NON-UNIQUE DEFAULT KEY,
BEGIN OF user_info,
username TYPE string,
firstname TYPE string,
lastname TYPE string,
middlename TYPE string,
gender TYPE string,
age TYPE string,
emails TYPE emails,
favouritefeature TYPE string,
features TYPE features,
addressinfo TYPE addresses,
END OF user_info,
users_info TYPE TABLE OF user_info INITIAL SIZE 0 WITH NON-UNIQUE DEFAULT KEY,
BEGIN OF json_doc,
odata TYPE string,
value TYPE users_info,
END OF json_doc.
*-------------------------------------------------------------------------------------------------------
* End of type declaration for JSON document structure
*-------------------------------------------------------------------------------------------------------
*-------------------------------------------------------------------------------------------------------
* Start of rest of the declaration for JSON document structure
*-------------------------------------------------------------------------------------------------------
TYPES:
BEGIN OF file_data,
line TYPE string,
END OF file_data.
TYPES:
file_datas TYPE TABLE OF file_data INITIAL SIZE 0.
DATA:
file_datas TYPE file_datas,
file_data TYPE file_data,
field_maps TYPE zconvert_json_to_abap=>field_mappings,
field_map TYPE zconvert_json_to_abap=>field_mapping,
json TYPE string,
json_2_abap TYPE json_doc,
filepaths TYPE filetable,
return_code TYPE i.
DATA:
json_converter TYPE REF TO zconvert_json_to_abap.
*-------------------------------------------------------------------------------------------------------
* End of rest of the declaration for JSON document structure
*-------------------------------------------------------------------------------------------------------
AT SELECTION-SCREEN ON VALUE-REQUEST FOR path.
cl_gui_frontend_services=>file_open_dialog(
EXPORTING
multiselection = abap_false
CHANGING
file_table = filepaths " Table Holding Selected Files
rc = return_code " Return Code, Number of Files or -1 If Error Occurred
EXCEPTIONS
file_open_dialog_failed = 1
cntl_error = 2
error_no_gui = 3
not_supported_by_gui = 4
OTHERS = 5
).
IF filepaths IS NOT INITIAL.
path = filepaths[ 1 ]-filename.
ENDIF.
START-OF-SELECTION.
cl_gui_frontend_services=>gui_upload(
EXPORTING
filename = path " Name of file
filetype = 'ASC' " File Type (ASCII, Binary)
CHANGING
data_tab = file_datas " Transfer table for file contents
EXCEPTIONS
file_open_error = 1
file_read_error = 2
no_batch = 3
gui_refuse_filetransfer = 4
invalid_type = 5
no_authority = 6
unknown_error = 7
bad_data_format = 8
header_not_allowed = 9
separator_not_allowed = 10
header_too_long = 11
unknown_dp_error = 12
access_denied = 13
dp_out_of_memory = 14
disk_full = 15
dp_timeout = 16
not_supported_by_gui = 17
error_no_gui = 18
OTHERS = 19
).
LOOP AT file_datas INTO file_data.
DATA(current_line) = sy-tabix.
REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>newline IN file_data-line WITH ''.
REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>horizontal_tab IN file_data-line WITH ''.
IF current_line EQ 1.
json = file_data-line.
ELSE.
CONCATENATE json file_data-line INTO json.
ENDIF.
ENDLOOP.
field_map-attribute_name = '@odata.context'.
field_map-alternate_name = 'ODATA'.
APPEND field_map TO field_maps.
CLEAR field_map.
field_map-attribute_name = '@odata.type'.
field_map-alternate_name = 'ODATA_TYPE'.
APPEND field_map TO field_maps.
CLEAR field_map.
"Create object of JSON converter
CREATE OBJECT json_converter
EXPORTING
json = json
field_mapping = field_maps.
"Convert data
json_converter->convert(
IMPORTING
abap = json_2_abap
).
BREAK-POINT.
We are done. Successfully converted JSON to ABAP.
I am really sorry to post the output in debugging, as displaying data using “CL_DEMO_OUTPUT” does not support nested data type.
Source Code of class ZCONVERT_JSON_TO_ABAP
class ZCONVERT_JSON_TO_ABAP definition
public
final
create public .
public section.
types:
BEGIN OF field_mapping,
attribute_name TYPE string,
alternate_name TYPE string,
END OF field_mapping .
types:
field_mappings TYPE TABLE OF field_mapping INITIAL SIZE 0 .
data JSON type STRING read-only .
data JSON_ORIGINAL type STRING read-only .
data JSON_XSTRING type XSTRING read-only .
constants NAME type STRING value 'name' ##NO_TEXT.
constants JSON_BEGIN type STRING value '{"STRUC":' ##NO_TEXT.
constants JSON_END type STRING value '}' ##NO_TEXT.
data FIELD_MAPS type FIELD_MAPPINGS read-only .
methods CONSTRUCTOR
importing
!JSON type STRING
!FIELD_MAPPING type FIELD_MAPPINGS optional .
methods CONVERT
exporting
value(ABAP) type DATA .
protected section.
private section.
methods CONVERT_STRING_TO_XSTRING
importing
!INPUT type STRING
returning
value(OUTPUT) type XSTRING .
methods CAPITALISE_JSON_ATTRIBUTES
importing
!INPUT type XSTRING
returning
value(OUTPUT) type XSTRING
raising
CX_SXML_STATE_ERROR
CX_SXML_PARSE_ERROR .
ENDCLASS.
CLASS ZCONVERT_JSON_TO_ABAP IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCONVERT_JSON_TO_ABAP->CAPITALISE_JSON_ATTRIBUTES
* +-------------------------------------------------------------------------------------------------+
* | [--->] INPUT TYPE XSTRING
* | [<-()] OUTPUT TYPE XSTRING
* | [!CX!] CX_SXML_STATE_ERROR
* | [!CX!] CX_SXML_PARSE_ERROR
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD capitalise_json_attributes.
DATA:
json_reader TYPE REF TO if_sxml_reader,
json_string_writer TYPE REF TO cl_sxml_string_writer,
json_writer TYPE REF TO if_sxml_writer,
node TYPE REF TO if_sxml_node,
node_open_element TYPE REF TO if_sxml_open_element.
DATA:
attributes TYPE if_sxml_attribute=>attributes,
root_flag_changed TYPE abap_bool.
FIELD-SYMBOLS:
<attribute> TYPE REF TO if_sxml_attribute.
DATA:
field_map TYPE field_mapping,
attribute_name TYPE string.
"Furnish a JSON reader
json_reader = cl_sxml_string_reader=>create( input = input ).
"Furnish a JSON writer
json_writer ?= cl_sxml_string_writer=>create( type = if_sxml=>co_xt_json ).
DO.
"Get node reference
node ?= json_reader->read_next_node( ).
IF node IS INITIAL.
EXIT.
ENDIF.
IF node->type = if_sxml_node=>co_nt_element_open.
node_open_element ?= node.
attributes = node_open_element->get_attributes( ).
LOOP AT attributes ASSIGNING <attribute>.
IF <attribute>->qname-name = name.
"Get attribute name
attribute_name = <attribute>->get_value( ).
"Check if special mapping provided for the field
READ TABLE field_maps INTO field_map WITH KEY attribute_name = attribute_name.
IF sy-subrc EQ 0.
<attribute>->set_value( to_upper( field_map-alternate_name ) ).
ELSE.
<attribute>->set_value( to_upper( <attribute>->get_value( ) ) ).
ENDIF.
ENDIF.
ENDLOOP.
ENDIF.
json_writer->write_node( node = node ).
ENDDO.
json_string_writer ?= json_writer.
output = json_string_writer->get_output( ) .
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCONVERT_JSON_TO_ABAP->CONSTRUCTOR
* +-------------------------------------------------------------------------------------------------+
* | [--->] JSON TYPE STRING
* | [--->] FIELD_MAPPING TYPE FIELD_MAPPINGS(optional)
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD constructor.
me->json_original = json.
me->field_maps = field_mapping.
CONCATENATE json_begin json json_end INTO me->json.
me->json_xstring = me->capitalise_json_attributes( me->convert_string_to_xstring( input = me->json ) ).
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCONVERT_JSON_TO_ABAP->CONVERT
* +-------------------------------------------------------------------------------------------------+
* | [<---] ABAP TYPE DATA
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD convert.
CALL TRANSFORMATION id SOURCE XML me->json_xstring
RESULT struc = abap.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCONVERT_JSON_TO_ABAP->CONVERT_STRING_TO_XSTRING
* +-------------------------------------------------------------------------------------------------+
* | [--->] INPUT TYPE STRING
* | [<-()] OUTPUT TYPE XSTRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD convert_string_to_xstring.
output = cl_abap_codepage=>convert_to( input ).
ENDMETHOD.
ENDCLASS.