Recently I had to fetch an attachment from SuccessFactors. The API returns a huge JSON. Here only a few lines of that json.
{
"d": {
"__metadata": {
"uri": "https://apihostname.successfactors.com:443/odata/v2/Attachment(1000)",
"type": "SFOData.Attachment"
},
"attachmentId": "1000",
"fileName": "Testdokument.pdf",
"mimeType": "application/pdf",
"moduleCategory": "HRIS_ATTACHMENT",
"userId": "10000555",
"fileExtension": "pdf",
"fileContent": "JVBERi0xLjcKjp2jtMXW5/gKMiAwIG9iag0KWy9JQ0NCYXNlZCAzIDAgUl0NCmVuZG9iag0KMyAw\r\nIG9iag0KPDwNCi9GaWx0ZXIgL0ZsYXRlRGVjb2RlIA0KL0xlbmd0aCAyNTk2IA0KL04gMyANCj4+\r\nDQpzdHJlYW0NCnicnZZ3VFPZFofPvTe9UJIQipTQa2hSAkgNvUiRLioxCRBKwJAAIjZEVHBEUZGm\r\nCDIo4ICjQ5GxIoqFAVGx6wQZRNRxcBQblklkrRnfvHnvzZvfH/d+a5+9z91n733WugCQ/IMFwkxY\r\nCYAMoVgU4efFiI2LZ2AHAQzwAANsAOBws7NCFvhGApkCfNiMbJkT+Be9ug4g+fsq0z+MwQD/n5S5\r\nWSIxAFCYjOfy+NlcGRfJOD1XnCW3T8mYtjRNzjBKziJZgjJWk3PyLFt89pllDznzMoQ8GctzzuJl\r\n8OTcJ+ONORK+jJFgGRfnCPi5Mr4mY4N0SYZAxm/ksRl8TjYAKJLcLuZzU2RsLWOSKDKCLeN5AOB......"
...
}
}
Parsing the JSON to ABAP
SAP provides two classes /ui2/cl_json
and /ui2/cl_data_access
to work with JSONs and there are also many cool tools on https://dotabap.org/ for that.
Because it’s just a simple JSON I tried to avoid importing another class to the system. So I stayed with SAPs classes.
First deserialize the JSON with /ui2/cl_json
.
" Constants for properties
CONSTANTS gc_filecontent TYPE string VALUE 'FILECONTENT' ##NO_TEXT.
CONSTANTS gc_filename TYPE string VALUE 'FILENAME' ##NO_TEXT.
CONSTANTS gc_doc_cat TYPE string VALUE 'DOCUMENTCATEGORY' ##NO_TEXT.
DATA: lr_data TYPE REF TO data.
/ui2/cl_json=>deserialize( EXPORTING json = lv_responce "the repsonse contains the json string
CHANGING data = lr_data ).
I found three similiar ways to use the class /ui2/cl_data_access
to get the relevant data out of the JSON.
1. Using the class construktor and the value( )
method for earch property.
DATA: lv_filecontent_base64 TYPE string,
lv_filename TYPE string,
lv_documentcategory TYPE string.
/ui2/cl_data_access=>create( ir_data = lr_data iv_component = |D-{ gc_filecontent }| )->value( IMPORTING ev_data = lv_filecontent_base64 ).
/ui2/cl_data_access=>create( ir_data = lr_data iv_component = |D-{ gc_filename }| )->value( IMPORTING ev_data = lv_filename).
/ui2/cl_data_access=>create( ir_data = lr_data iv_component = |D-{ gc_doc_cat }| )->value( IMPORTING ev_data = lv_documentcategory ).
2. Create an instance as first step and pass the component. Reuse the instance with the value( )
method.
DATA: lv_filecontent_base64 TYPE string,
lv_filename TYPE string,
lv_documentcategory TYPE string.
DATA(lo_data_access) = /ui2/cl_data_access=>create( ir_data = lr_data
iv_component = |D-| ).
lo_data_access->at( |{ gc_filecontent }| )->value( IMPORTING ev_data = lv_filecontent_base64 ).
lo_data_access->at( |{ gc_filename }| )->value( IMPORTING ev_data = lv_filename ).
lo_data_access->at( |{ gc_doc_cat }| )->value( IMPORTING ev_data = lv_documentcategory ).
3. Use the ref( )
method to get a reference of a property value. Because it returns a generic data type (and you cannot dereference generic references) you have to use CAST
combined with dereferencing (->*
) to get the concret value.
DATA(lo_data_access) = /ui2/cl_data_access=>create( ir_data = lr_data
iv_component = |D-| ).
DATA(lv_filecontent_base64) = CAST string( lo_data_access->at( gc_filecontent )->ref( ) )->*.
DATA(lv_filename) = CAST string( lo_data_access->at( gc_filename )->ref( ) )->*.
DATA(lv_documentcategory) = CAST string( lo_data_access->at( gc_doc_cat )->ref( ) )->*.
In the end I stayed with version 3 because I prefere using inline declarations. The complete result looked like this:
" Constants for properties
CONSTANTS gc_filecontent TYPE string VALUE 'FILECONTENT' ##NO_TEXT.
CONSTANTS gc_filename TYPE string VALUE 'FILENAME' ##NO_TEXT.
CONSTANTS gc_doc_cat TYPE string VALUE 'DOCUMENTCATEGORY' ##NO_TEXT.
DATA: lr_data TYPE REF TO data.
/ui2/cl_json=>deserialize( EXPORTING json = lv_response "the repsonse contains the json string
CHANGING data = lr_data ).
DATA(lo_data_access) = /ui2/cl_data_access=>create( ir_data = lr_data
iv_component = |D-| ).
DATA(lv_filecontent_base64) = CAST string( lo_data_access->at( gc_filecontent )->ref( ) )->*.
DATA(lv_filename) = CAST string( lo_data_access->at( gc_filename )->ref( ) )->*.
DATA(lv_documentcategory) = CAST string( lo_data_access->at( gc_doc_cat )->ref( ) )->*.
" Decode BASE64 PDF to XString
DATA(lv_decodedx) = cl_http_utility=>if_http_utility~decode_x_base64( lv_filecontent_base64 ).
" XString to binary
DATA(lt_data) = cl_bcs_convert=>xstring_to_solix( lv_decodedx ).
" Download file
cl_gui_frontend_services=>gui_download( EXPORTING bin_filesize = xstrlen( lv_decodedx )
filename = lv_filename
filetype = 'BIN'
show_transfer_status = ' '
CHANGING data_tab = lt_data ).
When /ui2/cl_data_access
is not available (SAP_UI has to be at least 7.51, see note 2526405), you can do it the oldschool way.
" Constants for properties
CONSTANTS gc_filecontent TYPE string VALUE 'FILECONTENT' ##NO_TEXT.
CONSTANTS gc_filename TYPE string VALUE 'FILENAME' ##NO_TEXT.
CONSTANTS gc_doc_cat TYPE string VALUE 'DOCUMENTCATEGORY' ##NO_TEXT.
DATA: lr_data TYPE REF TO data.
/ui2/cl_json=>deserialize( EXPORTING json = lv_response "the repsonse contains the json string
CHANGING data = lr_data ).
ASSIGN lr_data->* TO FIELD-SYMBOL(<fs_d>).
ASSIGN COMPONENT 'D' OF STRUCTURE <fs_d> TO FIELD-SYMBOL(<fs_components>).
ASSIGN <fs_components>->* TO FIELD-SYMBOL(<fs_fields>).
ASSIGN COMPONENT gc_doc_cat OF STRUCTURE <fs_fields> TO FIELD-SYMBOL(<documentcategory>).
ASSIGN COMPONENT gc_filename OF STRUCTURE <fs_fields> TO FIELD-SYMBOL(<filename>).
ASSIGN COMPONENT gc_filecontent OF STRUCTURE <fs_fields> TO FIELD-SYMBOL(<filecontent>).
ASSIGN <documentcategory>->* TO FIELD-SYMBOL(<documentcategory_value>).
ASSIGN <filename>->* TO FIELD-SYMBOL(<filename_value>).
ASSIGN <filecontent>->* TO FIELD-SYMBOL(<filecontent_value>).
"...