ABAP Tipp - Konvertierung JSON nach Intern
In diesem kleinen Tipp gehen wir darauf ein, wie du einen JSON Stream in ein internes Format konvertierst und dann ordentlich verwenden kannst.
Inhaltsverzeichnis
Wenn du heute an die Schnittstellenlandschaft denkst, wirst du wohl oder übel nicht an OData vorbeikommen. Ebenso wenn du ein Cloud System wie Ariba oder Successfactors anbinden möchtest. All diese Systeme haben gleich, dass sie mit REST Schnittstellen kommunizieren. Doch wie verarbeitest du die Antwortdaten, wenn du mal so einen Service per HTTP Request aus ABAP ansprichst? Darauf gehen wir heute etwas genauer ein.
Vorbereitung
Im ersten Schritt bauen wir uns ein passendes JSON auf, dass aus verschiedenen Datentypen besteht und dazu noch intern eine Tabelle verwendet, damit wir auch mit einer tiefen/verschachtelten Struktur arbeiten können. Unser Beispielaufbau könnte also wie folgt aussehen:
Nachdem wir nun ein zu konvertierenden JSON String haben, wollen wir dieses auch innerhalb eines Programmlaufs konvertieren und eine interne Struktur befüllen. Diese Struktur können wir uns ebenso im ABAP aufbauen, dazu benötigen wir verschiedene Typen.
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.
Strukturzuweisung
Dies ist die einfachste Form der Konvertierung, du musst allerdings immer wissen wie deine Daten aussehen, um so an den Inhalt heranzukommen. Der Aufbau von verschachtelten Strukturen kann ebenso einiges an Zeit in Anspruch nehmen. Im nächsten Schritt können wir den String bereits mit der Methode Parsen.
DATA:
ls_data TYPE ts_data.
/ui2/cl_json=>deserialize(
EXPORTING
json = id_json
CHANGING
data = ls_data
).
Wir erzeugen uns eine Struktur von unserem Zieltyp und verwenden dann die DESERIALIZE Methode der Klasse /UI2/CL_JSON, um die Konvertierung durchzuführen. Die Daten werden sauber in die Felder und die Tabelle gemappt, Element die in unserer Struktur fehlen, werden einfach übersprungen und wir können uns auf die wichtigen Inhalte konzentrieren.
Das Feld ADD_TEXT wird nicht gemappt, führt aber auch nicht zu einem Fehler. Das Feld BOOL wurde sauber nach ABAP_TRUE konvertiert. Damit steht uns eine einfache Art der Konvertierung zur Verfügung.
Generische Methode
Ebenso können wir eine Konvertierung komplett generisch durchführen, die Methode kann auch eine Referenz auf die Daten erzeugen, wenn wir keinen strukturierten Typen übergeben. Das Ergebnis einer solchen Konvertierung sieht im Eclipse Debugger wie folgt aus.
Auf solche komplett generischen Daten können wir nicht einfach zur Laufzeit zugreifen, da das System nicht weiß wie der Datentyp aufgebaut ist. Wir können aber über eine generische Verarbeitung auf die Daten zugreifen und diese mappen. In unserem Beispiel wollen wir einmal auf unser nicht benötigtes Element zugreifen und die Tabelle innerhalb der Struktur mappen.
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.
Ganz schön viel Code und viele ASSIGNs wirst du dir sicherlich denken. Jedes Element ist eine Referenz und muss doppelt gebunden werden, um an den eigentlichen Wert und die eigentlichen Daten zu gelangen. Du wirst uns sicherlich zustimmen, dass dieser Weg nicht besonders einfach ist und viele Fallen bereithält. Ebenso fehlt noch die gesamte Fehlerbehandlung im oberen Stück Quellcode, diese würde noch einmal die gleiche Anzahl Zeilen wegnehmen.
Beispiel
An dieser Stelle noch einmal die vollständige Beispielklasse, wie wir sie in den Beispielen verwenden.
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.
Fazit
Die Konvertierung eines JSON in ABAP kann sehr leicht von der Hand gehen, aber auch einiges an Kopfzerbrechen verursachen, wenn man es versucht dynamisch anzugehen. Wir empfehlen dir die Variante mit der vordefinierten Struktur, da diese viel einfacher umzusetzen ist.