Sunday, June 18, 2017

Convert internal table into binary Xstring (via JSON) and back

There is a lot of cases where binary data representation becomes handy. E.g. for some data transfers or saving data into database via rawstring format. No need to mention advantages of JSON over XML. The conversion within ABAP is described here.


There is a list of some conversion tools (the list is not comprehensive)

  1. CALL TRANSFORMATION ID - kernel general functionality
  2. CL_TREX_JSON_SERIALIZER & CL_TREX_JSON_DESERIALIZER - it seems it would be later absorbed by part of HANA = uncertain destiny
  3. /UI2/CL_JSON or CL_FDT_JSON - not reachable everywhere


I explored a bit the usage of Call transformation and Trex classes. You can observe my conclusions here below.


1) CALL TRANSFORMATION


This is general way working with two patches  starting from version of ABAP 7.02 on. Here you relay on kernel functionality - some internal binary black box coding :) Nevertheless it is working well. Of course you can write down your own XSLT transformation. Because as I wanted to have it simple as possible, finally I discovered a way without a need of custom transformation for deserialization.



DATAlo_writer           TYPE REF TO cl_sxml_string_writer,
      lv_xstring_json     TYPE xstring,
      " source itab - for serialization
      lt_flight           TYPE STANDARD TABLE OF spfli
      " target itab - after deserialization
      lt_flight2          TYPE STANDARD TABLE OF spfli



" Get data from database into itab
SELECT *
FROM spfli
UP TO ROWS
INTO TABLE lt_flight.

" Serialize data - into binary JSON
lo_writer cl_sxml_string_writer=>createif_sxml=>co_xt_json ).
TRY.
    CALL TRANSFORMATION id
    SOURCE itab lt_flight
    RESULT XML lo_writer.
  CATCH cx_xslt_format_error.
    MESSAGE 'Some serialization error occured.' TYPE 'E'.
ENDTRY.

" Now we have a serialized JSON data
lv_xstring_json lo_writer->get_output).



The back way - get back the itab


" Deserialization of data - back into itab
TRY.
    CALL TRANSFORMATION id
    SOURCE XML lv_xstring_json
    RESULT itab lt_flight2.
  CATCH cx_xslt_format_error.
    MESSAGE 'Some deserialization error occured.' TYPE 'E'.
ENDTRY.


" Final confirmation
ASSERT lt_flight EQ lt_flight2.



2) TREX classes


These classes are simple to use, pretty straight forward and easy to understand. The question is the future of them.

Get some data to be serialized


DATAlo_json_serializer   TYPE REF TO cl_trex_json_serializer,
            lo_json_deserializer TYPE REF TO cl_trex_json_deserializer,
            lt_flight            TYPE STANDARD TABLE OF spfli,
            lv_json              TYPE string,
            lv_binary            TYPE xstring.

SELECT *
  FROM spfli
  UP TO ROWS
  INTO TABLE lt_flight.


This is the source itab





Create JSON string out of itab


CREATE OBJECT lo_json_serializer
  EXPORTING
    data lt_flight.
lo_json_serializer->serialize).
lv_json lo_json_serializer->get_data).


Now we have a following JSON string.



Create a binary string out of JSON string


CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
  EXPORTING
    text     lv_json
*   MIMETYPE = ' '
*   ENCODING =
  IMPORTING
    buffer   lv_binary
  EXCEPTIONS
    failed   1.


There is nice binary content ready for a journey.




And now, the back way. Get itab out of binary data.


Get JSON string out of binary string

Note: I used for purpose different target variables.


DATAlt_flight2           TYPE STANDARD TABLE OF spfli,
            lv_json2             TYPE string


CALL FUNCTION 'HR_KR_XSTRING_TO_STRING'
  EXPORTING
    in_xstring lv_binary
  IMPORTING
    out_string lv_json2.



Get itab out of JSON string


CREATE OBJECT lo_json_deserializer.
lo_json_deserializer->deserialize(
  EXPORTING
    json   lv_json2
  IMPORTING
    abap   lt_flight2
 ).


" Final confirmation
ASSERT lt_flight EQ lt_flight2.




Now we are at the very beginning, holding exactly the same itab as at the beginning. Choose your own way ;-)


Useful links:





Sunday, June 4, 2017

Convert big PARID/PARVA table into big flat structure dynamically

Imagine, you can have a huge table with various keys PARID/PARVA similar to USR05 but defined for plant specific parameters (see below table structure).  There is an application working with BIG STRUCTURE receiving data from this database table. But how to do the conversion from table PAR(ID)/PAR(VAlue) into a structure? If you would do that manually, it would be huge list of IF ITAB-PARID = 'SOME_STRUCTURE_FIELD1' ELSE IF ITAB-PARID = 'SOME_STRUCTURE_FIELD2'  etc. - long list of checks made within a loop over ITAB. You would need to write down all the structure components one by one. Yes, it is nicely easily readable , but its ugly programming at all.

Let's look at the dynamic approach, it saves lots of manual coding here. The below code snippet is, I hope, well commented to grasp the idea.


Source database table structure:





Target filled flat structure:






*&---------------------------------------------------------------------*

REPORT z_dynamic_prg.

* Some huge flat target stucture receiving PARID/PARVA data from ITAB
TYPES:
  BEGIN OF tls_plant_specifics,
    delete_aufnr     TYPE abap_bool,
    use_screen_202   TYPE abap_bool,
    global_selection TYPE abap_bool,
    run_co04         TYPE abap_bool,
    not_use_opr_num  TYPE abap_bool,
    export_9_31      TYPE string,
    export_9_4       TYPE string,
    export_9_5       TYPE string,
    export_special   TYPE string,
    screen_907_clear TYPE string,
    not_use_fstau    TYPE abap_bool,
    blank_staging    TYPE abap_bool,
    filter_version   TYPE LENGTH 1,
    export_plan_in   TYPE abap_bool,
    staging_blank    TYPE abap_bool,
    remove_routines  TYPE abap_bool,
    panning_rmw_e3   TYPE abap_bool,
    schedule_old     TYPE abap_bool,
    free_time        TYPE LENGTH 1,
    set_subrc        TYPE abap_bool,
    zroh_1           TYPE string,
    zroh_2           TYPE abap_bool,
    fevor_set_bu     TYPE abap_bool,
    component_active TYPE abap_bool,
    sel_reservations TYPE string,
    mode_3_lgort     TYPE lgort_d,
    change_profil    TYPE abap_bool,
    set_user_attrib  TYPE abap_bool,
    import_check_lr  TYPE abap_bool,
    read_marc_as_1   TYPE werks_d,
    read_marc_as_2   TYPE werks_d,
    read_marc_as_3   TYPE werks_d,
    default_lgort    TYPE lgort_d,
    select_days      TYPE abap_bool,
    use_zmet_zroh    TYPE abap_bool,
    set_fevor_icon   TYPE string,
    read_marc_as_4   TYPE werks_d,
    set_fstad_fstau  TYPE abap_bool,
    set_okcode       TYPE string,
    set_affree       TYPE abap_bool,
    set_time         TYPE abap_bool,
    userexit_lgnum   TYPE lgnum,
    get_mto_mts_by   TYPE string,
  END OF tls_plant_specifics,

  BEGIN OF tls_plant_specifics_parva_parid,
    parid       TYPE memoryid,
    parva       TYPE xuvalue,
  END OF tls_plant_specifics_parva_parid.


DATAlt_plant_specifics_parva_parid TYPE STANDARD TABLE OF tls_plant_specifics_parva_parid,
      ls_plant_specifics_parva_parid LIKE LINE OF lt_plant_specifics_parva_parid,
      lo_structdescr     TYPE REF TO cl_abap_structdescr,
      lt_components      TYPE cl_abap_structdescr=>component_table,
      ls_component       LIKE LINE OF lt_components,
      lv_element_name    TYPE string,
      lv_element_content TYPE string.

DATAlv_werks       TYPE werks_d VALUE '9999', " some plant - hardcoded for test
      lv_plant_table TYPE tabname VALUE 'SOME_YOUR_PARAM_TABLE_NAME'
      ls_plant_specifics TYPE tls_plant_specifics" target structure

FIELD-SYMBOLS <fs_element> TYPE simple.

" Get all plant parameters into itab
SELECT parid parva FROM (lv_plant_table)
INTO TABLE lt_plant_specifics_parva_parid
WHERE werks lv_werks.


IF sy-subrc EQ AND lt_plant_specifics_parva_parid IS NOT INITIAL ).

  " Get the values out of table (parid/parva) into class public structure
  lo_structdescr ?= cl_abap_structdescr=>describe_by_datals_plant_specifics ).
  lt_components   lo_structdescr->get_components).

  " Loop at parid x parva itab (comming from database)
  LOOP AT lt_plant_specifics_parva_parid INTO ls_plant_specifics_parva_parid.
 
    " Loop over components of target structure
    LOOP AT lt_components INTO ls_component.

      " Read component name out of table
      lv_element_name ls_plant_specifics_parva_parid-parid.
   
      " If PARAM name is equal to STRUCTURE component name => assign the value to the structure for this field
      IF ls_component-name EQ lv_element_name

        CONCATENATE 'ls_plant_specifics-' lv_element_name INTO lv_element_name.
        ASSIGN (lv_element_nameTO <fs_element>.

        IF <fs_element> IS ASSIGNED AND ls_plant_specifics_parva_parid-parva IS NOT INITIAL ).
       
          " You can assign data based on its type...   
          IF ls_component-type->absolute_name CS 'ABAP_BOOL'.
            " just one char
            <fs_element> ls_plant_specifics_parva_parid-parva+0(1).
          ELSE.
            " strings, etc.
            lv_element_content ls_plant_specifics_parva_parid-parva.
            CONDENSE lv_element_content NO-GAPS.
            <fs_element> lv_element_content.
          ENDIF.

        ENDIF.
      ENDIF.

    ENDLOOP.
  ENDLOOP.

  FREE lo_structdescr.

ENDIF.