Homelab, Linux, JS & ABAP (~˘▾˘)~
 

[SAPUI5] Parse error message

If the error response is type json:

        oModel.callFunction("/myEntity", {
          method: "GET",
          urlParameters: {
            ID: myID,
          },
          success: oData => console.log(oData),
          error: oError => MessageBox.error(JSON.parse(oError.responseText).error.message.value, { title: "An error occurred" })
        });

If the error response is coming from a Gateway and has an XML body (link):

 MessageBox.error(jQuery.parseXML(oError.response.body).querySelector("message").textContent)

[Postman] Visualize base64 image

If you have a service which returns a payload like the following (including a base64 encoded jpeg) you can display it directly in postman.

{
        "photo": "/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsK\r\nCwsND..............",
        "photoId": "192",
        "mimeType": "image/jpeg"
}

This can be done with a few lines of code. In Postman navigate to the “Tests” tab:

and insert the following lines:

//output to postman console
console.log("PhotoId: " + pm.response.json()["photoId"]);
console.log("Base64: " + pm.response.json()["photo"]);

//output in visualize tab
let template = `<img src='{{img}}'/>`;

pm.visualizer.set(template, { 
    img: `data:image/jpeg;base64,${pm.response.json()["photo"]}`
});

In the “Visualize” tab you should now find your image

[ABAP] JSON to ABAP with dereferencing

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>).

    "...

[ABAP] Convert JavaScript Timestamp to yyyy-MM-dd’T’HH:mm:ss

DATA(lv_js_timestamp) = "/Date(1615161600000)/".

"Extract /Date(1615161600000)/ to 1615161600000
FIND REGEX '([0-9]+)' IN lv_js_timestamp IGNORING CASE SUBMATCHES DATA(js_timestamp).

cl_pco_utility=>convert_java_timestamp_to_abap( EXPORTING iv_timestamp = js_timestamp
                                                IMPORTING ev_date      = DATA(lv_date)
                                                          ev_time      = DATA(lv_time) ).
"2021-03-08T00:00:00
CONVERT DATE lv_date TIME lv_time INTO TIME STAMP DATA(timestamp) TIME ZONE 'UTC'.
rv_datetime = |{ timestamp TIMESTAMP = ISO }|.