ABAP Quick - Convert JSON to internal
In this little tip, we'll go into how you can convert a JSON stream to an internal format and then use it properly.
Table of contents
If you think about the interface landscape today, you won't be able to get around OData for better or for worse. Likewise if you want to connect a cloud system like Ariba or Successfactors. What all these systems have in common is that they communicate with REST interfaces. But how do you process the response data when you address such a service with an HTTP request from ABAP? We'll go into that in a little more detail today.
Preperation
In the first step, we build a suitable JSON that consists of different data types and uses a table internally so that we can also work with a deep/nested structure. Our example structure could look like this:
Now that we have a JSON string to be converted, we also want to convert this within a program run and fill an internal structure. We can also set up this structure in ABAP, for which we need different types.
TYPES:
BEGIN OF ts_subdata,
key TYPE string,
value TYPE string,
END OF ts_subdata,
tt_subdata TYPE STANDARD TABLE OF ts_subdata WITH EMPTY KEY,
BEGIN OF ts_data,
text TYPE string,
number TYPE i,
bool TYPE abap_bool,
table TYPE tt_subdata,
END OF ts_data.
Structure assignment
This is the easiest form of conversion, but you always have to know what your data looks like in order to access the content. Building nested structures can also take some time. In the next step we can already parse the string with the method.
DATA:
ls_data TYPE ts_data.
/ui2/cl_json=>deserialize(
EXPORTING
json = id_json
CHANGING
data = ls_data
).
We create a structure of our target type and then use the DESERIALIZE method of the class /UI2/CL_JSON to carry out the conversion. The data is neatly mapped into the fields and the table, elements that are missing in our structure are simply skipped and we can concentrate on the important content.
The ADD_TEXT field is not mapped, but does not lead to an error either. The BOOL field was properly converted to ABAP_TRUE. This provides us with a simple type of conversion.
Generic way
We can also perform a conversion completely generically; the method can also generate a reference to the data if we do not pass a structured type. The result of such a conversion looks like this in the Eclipse debugger.
We cannot simply access such completely generic data at runtime because the system does not know how the data type is structured. However, we can access and map the data via generic processing. In our example we want to access our unneeded element and map the table within the structure.
DATA:
lr_data TYPE REF TO data,
ld_add_text TYPE string,
lt_subdata TYPE tt_subdata.
FIELD-SYMBOLS:
<lt_table> TYPE STANDARD TABLE.
/ui2/cl_json=>deserialize(
EXPORTING
json = id_json
CHANGING
data = lr_data
).
ASSIGN lr_data->* TO FIELD-SYMBOL(<ls_data>).
" Map the ADD_TEXT field
ASSIGN COMPONENT 'ADD_TEXT' OF STRUCTURE <ls_data> TO FIELD-SYMBOL(<ld_add>).
ASSIGN <ld_add>->* TO FIELD-SYMBOL(<ld_add_value>).
ld_add_text = <ld_add_value>.
" Map internal table
ASSIGN COMPONENT 'TABLE' OF STRUCTURE <ls_data> TO FIELD-SYMBOL(<lt_table_ref>).
ASSIGN <lt_table_ref>->* TO <lt_table>.
LOOP AT <lt_table> ASSIGNING FIELD-SYMBOL(<ls_line>).
ASSIGN <ls_line>->* TO FIELD-SYMBOL(<ls_line_value>).
ASSIGN COMPONENT 'KEY' OF STRUCTURE <ls_line_value> TO FIELD-SYMBOL(<ld_key>).
ASSIGN COMPONENT 'VALUE' OF STRUCTURE <ls_line_value> TO FIELD-SYMBOL(<ld_value>).
ASSIGN <ld_key>->* TO FIELD-SYMBOL(<ld_key_value>).
ASSIGN <ld_value>->* TO FIELD-SYMBOL(<ld_value_value>).
INSERT VALUE #(
key = <ld_key_value>
value = <ld_value_value>
) INTO TABLE lt_subdata.
ENDLOOP.
You will certainly think of a lot of code and many ASSIGNs. Each element is a reference and must be double-bound in order to get to the actual value and the actual data. You will surely agree with us that this path is not particularly easy and that it has many pitfalls. Likewise, the entire error handling in the upper part of the source code is still missing, this would take away the same number of lines again.
Example
Here again the complete example class as we use it in the examples.
CLASS zcl_60bs_test_json_convert DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
TYPES:
BEGIN OF ts_subdata,
key TYPE string,
value TYPE string,
END OF ts_subdata,
tt_subdata TYPE STANDARD TABLE OF ts_subdata WITH EMPTY KEY,
BEGIN OF ts_data,
text TYPE string,
number TYPE i,
bool TYPE abap_bool,
table TYPE tt_subdata,
END OF ts_data.
METHODS:
convert_json_with_structured
IMPORTING
id_json TYPE string,
convert_json_with_reference
IMPORTING
id_json TYPE string.
ENDCLASS.
CLASS zcl_60bs_test_json_convert IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(ld_json) = `{"text":"Some text","number":123, "bool":"TRUE", "add_text":"Text number 2", "table":[{"key":"One","value":"1"},{"key":"Two","value":"2"}]}`.
convert_json_with_structured( ld_json ).
convert_json_with_reference( ld_json ).
ENDMETHOD.
METHOD convert_json_with_structured.
DATA:
ls_data TYPE ts_data.
/ui2/cl_json=>deserialize(
EXPORTING
json = id_json
CHANGING
data = ls_data
).
ENDMETHOD.
METHOD convert_json_with_reference.
DATA:
lr_data TYPE REF TO data,
ld_add_text TYPE string,
lt_subdata TYPE tt_subdata.
FIELD-SYMBOLS:
<lt_table> TYPE STANDARD TABLE.
/ui2/cl_json=>deserialize(
EXPORTING
json = id_json
CHANGING
data = lr_data
).
ASSIGN lr_data->* TO FIELD-SYMBOL(<ls_data>).
ASSIGN COMPONENT 'ADD_TEXT' OF STRUCTURE <ls_data> TO FIELD-SYMBOL(<ld_add>).
ASSIGN <ld_add>->* TO FIELD-SYMBOL(<ld_add_value>).
ld_add_text = <ld_add_value>.
ASSIGN COMPONENT 'TABLE' OF STRUCTURE <ls_data> TO FIELD-SYMBOL(<lt_table_ref>).
ASSIGN <lt_table_ref>->* TO <lt_table>.
LOOP AT <lt_table> ASSIGNING FIELD-SYMBOL(<ls_line>).
ASSIGN <ls_line>->* TO FIELD-SYMBOL(<ls_line_value>).
ASSIGN COMPONENT 'KEY' OF STRUCTURE <ls_line_value> TO FIELD-SYMBOL(<ld_key>).
ASSIGN COMPONENT 'VALUE' OF STRUCTURE <ls_line_value> TO FIELD-SYMBOL(<ld_value>).
ASSIGN <ld_key>->* TO FIELD-SYMBOL(<ld_key_value>).
ASSIGN <ld_value>->* TO FIELD-SYMBOL(<ld_value_value>).
INSERT VALUE #(
key = <ld_key_value>
value = <ld_value_value>
) INTO TABLE lt_subdata.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
Conclusion
Converting a JSON to ABAP can be very easy, but it can also cause a lot of headache if you try to approach it dynamically. We recommend the variant with the predefined structure, as this is much easier to implement.