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.
Friday, February 2, 2018
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_JSON) type STRING .
class-methods SERIALIZE_CHECK
importing
!I_ABAP type DATA
exporting
!E_V_JSON type STRING
returning
value(R_V_SUCCESS) type 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_VALUE) type 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_PART) type STRING_TABLE .
class-methods UNPACK
importing
!I_V_JSON type STRING
returning
value(R_V_JSON) type 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_data( i_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 = unpack( i_v_json ). " remove quotation marks
lv_value = cl_http_utility=>unescape_url( lv_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_data( e_abap ) ).
" get list of json components
lt_component = get_components_of_structure( i_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 = unpack( i_v_json ). " remove braces [ ]
lt_json = zcl_jso_serial=>split( lv_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 = unpack( i_v_json ).
" split at comma
lt_part = zcl_jso_serial=>split( lv_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 strlen( i_v_json ) > 0.
lv_i = 0.
lv_start = 0.
lv_len = 0.
WHILE lv_i < strlen( i_v_json ).
lv_c = i_v_json+lv_i(1).
ADD 1 TO lv_len.
IF lv_c = '['. ADD 1 TO lv_b1_count. ENDIF.
IF lv_c = ']'. SUBTRACT 1 FROM lv_b1_count. ENDIF.
IF lv_c = '{'. ADD 1 TO lv_b2_count. ENDIF.
IF lv_c = '}'. SUBTRACT 1 FROM lv_b2_count. ENDIF.
IF ( lv_c = i_v_splitter OR lv_i = strlen( i_v_json ) - 1 ) AND lv_b1_count = 0 AND lv_b2_count = 0.
IF lv_i < strlen( i_v_json ) - 1. SUBTRACT 1 FROM lv_len. ENDIF.
INSERT substring( val = i_v_json off = lv_start len = lv_len ) INTO TABLE r_t_part.
lv_start = lv_i + 1.
lv_len = 0.
ENDIF.
ADD 1 TO lv_i.
ENDWHILE.
ENDMETHOD.
METHOD unpack.
DATA lv_first.
DATA lv_last.
" validity checks
IF strlen( i_v_json ) < 2.
RAISE EXCEPTION TYPE cx_xslt_deserialization_error.
ENDIF.
" get first and last character
lv_first = i_v_json(1).
lv_last = substring( val = i_v_json off = strlen( i_v_json ) - 1 len = 1 ).
IF ( lv_first = '"' AND lv_last = '"' ) OR
( lv_first = '[' AND lv_last = ']' ) OR
( lv_first = '{' AND lv_last = '}' ).
" unpack
r_v_json = substring( val = i_v_json off = 1 len = strlen( i_v_json ) - 2 ).
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 = serialize( i_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.
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_JSON) type STRING .
class-methods SERIALIZE_CHECK
importing
!I_ABAP type DATA
exporting
!E_V_JSON type STRING
returning
value(R_V_SUCCESS) type 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_VALUE) type 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_PART) type STRING_TABLE .
class-methods UNPACK
importing
!I_V_JSON type STRING
returning
value(R_V_JSON) type 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_data( i_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 = unpack( i_v_json ). " remove quotation marks
lv_value = cl_http_utility=>unescape_url( lv_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_data( e_abap ) ).
" get list of json components
lt_component = get_components_of_structure( i_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 = unpack( i_v_json ). " remove braces [ ]
lt_json = zcl_jso_serial=>split( lv_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 = unpack( i_v_json ).
" split at comma
lt_part = zcl_jso_serial=>split( lv_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 strlen( i_v_json ) > 0.
lv_i = 0.
lv_start = 0.
lv_len = 0.
WHILE lv_i < strlen( i_v_json ).
lv_c = i_v_json+lv_i(1).
ADD 1 TO lv_len.
IF lv_c = '['. ADD 1 TO lv_b1_count. ENDIF.
IF lv_c = ']'. SUBTRACT 1 FROM lv_b1_count. ENDIF.
IF lv_c = '{'. ADD 1 TO lv_b2_count. ENDIF.
IF lv_c = '}'. SUBTRACT 1 FROM lv_b2_count. ENDIF.
IF ( lv_c = i_v_splitter OR lv_i = strlen( i_v_json ) - 1 ) AND lv_b1_count = 0 AND lv_b2_count = 0.
IF lv_i < strlen( i_v_json ) - 1. SUBTRACT 1 FROM lv_len. ENDIF.
INSERT substring( val = i_v_json off = lv_start len = lv_len ) INTO TABLE r_t_part.
lv_start = lv_i + 1.
lv_len = 0.
ENDIF.
ADD 1 TO lv_i.
ENDWHILE.
ENDMETHOD.
METHOD unpack.
DATA lv_first.
DATA lv_last.
" validity checks
IF strlen( i_v_json ) < 2.
RAISE EXCEPTION TYPE cx_xslt_deserialization_error.
ENDIF.
" get first and last character
lv_first = i_v_json(1).
lv_last = substring( val = i_v_json off = strlen( i_v_json ) - 1 len = 1 ).
IF ( lv_first = '"' AND lv_last = '"' ) OR
( lv_first = '[' AND lv_last = ']' ) OR
( lv_first = '{' AND lv_last = '}' ).
" unpack
r_v_json = substring( val = i_v_json off = 1 len = strlen( i_v_json ) - 2 ).
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 = serialize( i_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.
Subscribe to:
Posts (Atom)
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 ...
-
Sometimes the backend doesn't return the selected rows of an SALV. For my case get_metadata( ) was the solution: go_salv-> get_met...
-
GUIDs or UUIDs can be created with the class cl_system_uuid . try . data (lv_uuid) = cl_system_uuid => if_system_uuid_static...
-
AA PR-AUF(E) Prozessauftrag (eröffnet) AB PR-AUF(F) Prozessauftrag (freigegeben) AC FE...