Friday, February 2, 2018

Compress data with ABAP (ZIP, GZIP)

    DATA lv_string TYPE string.
    DATA lv_xstring TYPE xstring.


    " gzip 
    cl_abap_gzip=>compress_text(
      EXPORTING text_in  lv_string
      IMPORTING gzip_out lv_xstring
    ).



    " unzip 
    cl_abap_gzip=>decompress_text(
      EXPORTING gzip_in  lv_xstring
      IMPORTING text_out lv_string
    ).



See also methods COMPRESS_BINARY and DECOMPRESS_BINARY of cl_abap_gzip.

Robust ABAP to JSON serializer

SAP provides some classes to serialize and deserialize arbitrary ABAP variables. Unfortunately all of them have some disadvantages. The classes are often to complex to use. One method call should be enough to serialize. It is not necessary to call constructor and multiple methods. But a real problem of most classes is, that they just dump for some data constallations, e.g. if the variable contains json Control character like [, ], { or }. Even the colon can cause Problems.


Since the implementaion is easy, we made our own. I am sure that also our own implementation will have bugs... but we can correct them quickly, what is not possible with the SAP's standard classes


class ZCL_JSO_SERIAL definition
  public
  create public .

public section.
  types:
    BEGIN OF yt_s_component_value .
    TYPES   name TYPE string.
    TYPES   value TYPE string.
    TYPES END OF yt_s_component_value .
  types:
    yt_t_component_value TYPE SORTED TABLE OF yt_s_component_value WITH UNIQUE KEY name .

  class-methods SERIALIZE
    importing
      !I_ABAP type DATA
    returning
      value(R_V_JSONtype STRING .
  class-methods SERIALIZE_CHECK
    importing
      !I_ABAP type DATA
    exporting
      !E_V_JSON type STRING
    returning
      value(R_V_SUCCESStype ABAP_BOOL .
  class-methods DESERIALIZE
    importing
      !I_V_JSON type STRING
    exporting
      !E_ABAP type DATA
    raising
      CX_XSLT_DESERIALIZATION_ERROR .




protected section.
  class-methods GET_COMPONENTS_OF_STRUCTURE
    importing
      !I_V_JSON type STRING
    returning
      value(R_T_COMPONENT_VALUEtype YT_T_COMPONENT_VALUE
    raising
      CX_XSLT_DESERIALIZATION_ERROR .

  class-methods SPLIT
    importing
      !I_V_JSON type STRING
      !I_V_SPLITTER type CHAR1 default ','
    returning
      value(R_T_PARTtype STRING_TABLE .

  class-methods UNPACK
    importing
      !I_V_JSON type STRING
    returning
      value(R_V_JSONtype STRING
    raising
      CX_XSLT_DESERIALIZATION_ERROR .




  METHOD serialize.

    DATA lv_type.
    DATA lv_item_count TYPE i.
    DATA lo_structdescr TYPE REF TO cl_abap_structdescr.
    DATA lv_index TYPE syindex.
    FIELD-SYMBOLS <ls_compdescr> TYPE abap_compdescr.
    FIELD-SYMBOLS <lv_item> TYPE any.
    FIELD-SYMBOLS <lt_abap> TYPE ANY TABLE.

    DESCRIBE FIELD i_abap TYPE lv_type COMPONENTS lv_item_count.

    IF lv_type <> cl_abap_typedescr=>typekind_table.
      IF lv_item_count 0.
*** unstructured (plain field)
        r_v_json i_abap" This assignment can cause problems. Monitor carefully!
        r_v_json |"{ cl_http_utility=>escape_url( r_v_json ) }"|.
      ELSE.
*** structured
        r_v_json '{'.
        lo_structdescr CAST #cl_abap_typedescr=>describe_by_datai_abap ).
        LOOP AT lo_structdescr->components ASSIGNING <ls_compdescr>.
          lv_index sy-tabix .
          ASSIGN COMPONENT <ls_compdescr>-name OF STRUCTURE i_abap TO <lv_item> .

          r_v_json |{ r_v_json }| &&
            |{ to_lower<ls_compdescr>-name }:| &&
            |{ serialize<lv_item> }|.

          IF lv_index < lv_item_count.
            r_v_json |{ r_v_json },|.
          ENDIF .
        ENDLOOP .
        r_v_json |{ r_v_json }\}|.
      ENDIF.
    ELSE.
*** internal table
      r_v_json '['.
      ASSIGN i_abap TO <lt_abap>.
      LOOP AT <lt_abap> ASSIGNING <lv_item>.
        lv_index sy-tabix .

        r_v_json |{ r_v_json }{ serialize<lv_item> }|.

        IF lv_index < lines<lt_abap> ).
          r_v_json |{ r_v_json },|.
        ENDIF .
      ENDLOOP.
      r_v_json |{ r_v_json }]|.
    ENDIF.

  ENDMETHOD.




  METHOD deserialize.

    DATA lv_type.
    DATA lv_item_count TYPE i.
    DATA lv_value TYPE string.
    DATA lv_json TYPE string.
    DATA lo_structdescr TYPE REF TO cl_abap_structdescr.
    DATA ls_component TYPE yt_s_component_value.
    DATA lt_component TYPE yt_t_component_value.
    DATA lt_json TYPE string_table.
    DATA lr_row TYPE REF TO data.

    FIELD-SYMBOLS <ls_compdescr> TYPE abap_compdescr.
    FIELD-SYMBOLS <lv_item> TYPE any.
    FIELD-SYMBOLS <lt_abap> TYPE ANY TABLE.
    FIELD-SYMBOLS <l_abap> TYPE any.

    CLEAR e_abap.
    DESCRIBE FIELD e_abap TYPE lv_type COMPONENTS lv_item_count.

    IF lv_type <> cl_abap_typedescr=>typekind_table.
      IF lv_item_count 0.
*** unstructured
        lv_value unpacki_v_json )" remove quotation marks
        lv_value cl_http_utility=>unescape_urllv_value ).

        e_abap lv_value.
      ELSE" unstructured i_v_json (plain variable)
*** structured
        " get component descriptor for abap object
        lo_structdescr CAST #cl_abap_typedescr=>describe_by_datae_abap ).
        " get list of json components
        lt_component get_components_of_structurei_v_json ).
        " iterate components of abap structure
        LOOP AT lo_structdescr->components ASSIGNING <ls_compdescr>.
          " assign json components to components of abap structure
          ASSIGN COMPONENT <ls_compdescr>-name OF STRUCTURE e_abap TO <lv_item> .
          CLEAR ls_component.
          READ TABLE lt_component INTO ls_component
            WITH TABLE KEY name to_lower<ls_compdescr>-name ).
          IF sy-subrc 0.
            deserialize(
              EXPORTING i_v_json ls_component-value
              IMPORTING e_abap <lv_item>
            ).
          ELSE.
            CLEAR <lv_item>.
          ENDIF.
        ENDLOOP" components of structure
      ENDIF" structured i_v_json
    ELSE.
*** internal table
      " get field symbol for table
      ASSIGN e_abap TO <lt_abap>.

      " create work area for table
      CREATE DATA lr_row LIKE LINE OF <lt_abap>.
      ASSIGN lr_row->TO <l_abap>.

      " get table of json strings
      lv_json unpacki_v_json )" remove braces [ ]
      lt_json zcl_jso_serial=>splitlv_json )" split at comma

      " iterate json strings and deserialize each
      LOOP AT lt_json INTO lv_json.
        deserialize(
          EXPORTING i_v_json lv_json
          IMPORTING e_abap <l_abap>
        ).
        INSERT <l_abap> INTO TABLE <lt_abap>" collect result
      ENDLOOP.
    ENDIF" i_v_json contains data of internal table

  ENDMETHOD.




  METHOD get_components_of_structure.

    DATA lv_name TYPE string.
    DATA lv_value TYPE string.
    DATA lv_s TYPE string.
    DATA lt_part TYPE string_table.

    " remove braces { }
    lv_s unpacki_v_json ).

    " split at comma
    lt_part zcl_jso_serial=>splitlv_s ).

    " split name and value at colon
    LOOP AT lt_part INTO lv_s.
      SPLIT lv_s AT ':' INTO lv_name lv_value.
      INSERT VALUE #name lv_name  value lv_value INTO TABLE r_t_component_value.
    ENDLOOP.

  ENDMETHOD.




  METHOD split.

    DATA lv_i TYPE i.
    DATA lv_start TYPE i.
    DATA lv_len TYPE i.
    DATA lv_c.
    DATA lv_b1_count TYPE i.
    DATA lv_b2_count TYPE i.

    CHECK strleni_v_json 0.

    lv_i 0.
    lv_start 0.
    lv_len 0.
    WHILE lv_i < strleni_v_json ).
      lv_c i_v_json+lv_i(1).
      ADD TO lv_len.

      IF lv_c '['ADD TO lv_b1_countENDIF.
      IF lv_c ']'SUBTRACT FROM lv_b1_countENDIF.
      IF lv_c '{'ADD TO lv_b2_countENDIF.
      IF lv_c '}'SUBTRACT FROM lv_b2_countENDIF.

      IF lv_c i_v_splitter OR lv_i strleni_v_json AND lv_b1_count AND lv_b2_count 0.
        IF lv_i < strleni_v_json 1SUBTRACT FROM lv_lenENDIF.
        INSERT substringval i_v_json  off lv_start  len lv_len INTO TABLE r_t_part.
        lv_start lv_i + 1.
        lv_len 0.
      ENDIF.

      ADD TO lv_i.
    ENDWHILE.

  ENDMETHOD.




  METHOD unpack.

    DATA lv_first.
    DATA lv_last.

    " validity checks
    IF strleni_v_json 2.
      RAISE EXCEPTION TYPE cx_xslt_deserialization_error.
    ENDIF.

    " get first and last character
    lv_first i_v_json(1).
    lv_last  substringval i_v_json off strleni_v_json len ).

    IF lv_first '"' AND lv_last '"' OR
       lv_first '[' AND lv_last ']' OR
       lv_first '{' AND lv_last '}' ).
      " unpack
      r_v_json substringval i_v_json off len strleni_v_json ).
    ELSE.
      RAISE EXCEPTION TYPE cx_xslt_deserialization_error.
    ENDIF.

  ENDMETHOD.




  METHOD serialize_check.

    DATA lr_abap TYPE REF TO data.

    FIELD-SYMBOLS <l_abap> TYPE any.

    " create a second variable <l_abap> with the same type like i_abap
    CREATE DATA lr_abap LIKE i_abap.
    ASSIGN lr_abap->TO <l_abap>.

  TRY. 
  " serialize i_abap to e_v_json
    e_v_json serializei_abap ).

    " deserialize e_v_json to <l_abap>
    deserialize(
      exporting i_v_json e_v_json
      importing e_abap   <l_abap>
    ).
    CATCH cx_xslt_deserialization_error.
      r_v_success abap_false.
      RETURN.
  ENDTRY.


    " now i_abap and <l_abap> shall be equal
    IF i_abap <l_abap>.
      r_v_success abap_true.
    ELSE.
      r_v_success abap_false.
    ENDIF.

  ENDMETHOD.

SAP ABAP: Determine Timezone for Plant

    DATA:       lt_tzone TYPE STANDARD TABLE OF tznzone WITH DEFAULT KEY,       l_tzone  TYPE tznzone.     " get time zone for plant   ...