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

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

    "...