ABAP - Type Casting
How do you actually get the original type of a class or instance if it is passed in a generic table? In this article we examine the possibilities.
Table of contents
When passing a generic table with many instances, in some situations you may also want to obtain further information from the object. In such cases you have to perform a CAST to change the type and thus the access options.
Introduction
In object-oriented development, you often work with objects that inherit from an interface, but then also contain specific information. Such objects are usually managed in tables of "unspecific" type so that all types of objects can be stored and so that a separate table is not needed for each object. In this example we will look at the new application log and try to access the various contents of the messages.
Preparation
In the first step we create a log, using the example from the article for the new application log to create a message handle. We can now read this to get the news.
DATA(lt_messages) = cl_bali_log_db=>get_instance( )->load_log( c_handle )->get_all_items( ).
After reading the messages, we get the LT_MESSAGES table with different references to different objects:
Find Interface
Before we can start processing, we should find the right interface. Once we have an object, we don't need to recreate it in the appropriate type, we just need to cast it to the appropriate type. In most cases we use the interface for this, as it is the public representation of the class to the outside world, with all the methods and attributes we can use. Let’s take a closer look at the class CL_BALI_MESSAGE_GETTER:
The class has two interfaces, which provide various attributes and methods. In the class, however, the entirety of both interfaces is available to us. The interface IF_BALI_ITEM_GETTER seems to be the generic interface as it provides general information about the log item, such as SEVERITY, TIMESTAMP or the GET_MESSAGE_TEXT method.
If we now look at the interface IF_BALI_MESSAGE_GETTER, we will see specific attributes that describe a T100 message, such as the message variables or the message ID.
This means that for the following processing, we need the specific interfaces to get the dependent information of the messages and not just the "text".
Variant 0 - Classic
The classic variant comes in different versions, you could try to get the information about the type via the type description (RTTI) or you could try a CAST on the corresponding type. In this example we first try a simple CAST, but do not use the old variant "?=" of the assignment.
DATA(lo_message) = CAST if_bali_message_getter( ls_message-item ).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
Since we are working with several types here, it can also happen that the cast does not work. If this is the case, an exception of type CX_SY_MOVE_CAST_ERROR is generated. We should catch this exception and use TRY/CATCH to do this.
TRY.
DATA(lo_message) = CAST if_bali_message_getter( ls_message-item ).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
CATCH cx_sy_move_cast_error.
ENDTRY.
If this worked, we can end the loop with CONTINUE, as another cast makes no sense. We repeat this accordingly for the other types in order to obtain the specific information. The final loop could look like this:
LOOP AT it_messages INTO DATA(ls_message).
TRY.
DATA(lo_message) = CAST if_bali_message_getter( ls_message-item ).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
CONTINUE.
CATCH cx_sy_move_cast_error.
ENDTRY.
TRY.
DATA(lo_free_text) = CAST if_bali_free_text_getter( ls_message-item ).
io_out->write( lo_free_text->get_message_text( ) ).
CONTINUE.
CATCH cx_sy_move_cast_error.
ENDTRY.
TRY.
DATA(lo_exception) = CAST if_bali_exception_getter( ls_message-item ).
io_out->write( lo_exception->exception_class ).
io_out->write( lo_exception->exception_id_name ).
io_out->write( lo_exception->get_message_text( ) ).
CONTINUE.
CATCH cx_sy_move_cast_error.
ENDTRY.
ENDLOOP.
Variant 1 - IS INSTANCE
The variant with IS INSTANCE offers a slightly simpler way and without exception handling. We can use an IF statement to query whether the object can be assigned to a corresponding class or interface. If this is the case, we can carry out a cast.
IF ls_message-item IS INSTANCE OF if_bali_message_getter.
DATA(lo_message) = CAST if_bali_message_getter( ls_message-item ).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
ENDIF.
We can chain the queries and thus save error handling and continuing, the processing looks much tidier at the end:
LOOP AT it_messages INTO DATA(ls_message).
io_out->write( `-` ).
IF ls_message-item IS INSTANCE OF if_bali_message_getter.
DATA(lo_message) = CAST if_bali_message_getter( ls_message-item ).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
ELSEIF ls_message-item IS INSTANCE OF if_bali_free_text_getter.
DATA(lo_free_text) = CAST if_bali_free_text_getter( ls_message-item ).
io_out->write( lo_free_text->get_message_text( ) ).
ELSEIF ls_message-item IS INSTANCE OF if_bali_exception_getter.
DATA(lo_exception) = CAST if_bali_exception_getter( ls_message-item ).
io_out->write( lo_exception->exception_class ).
io_out->write( lo_exception->exception_id_name ).
io_out->write( lo_exception->get_message_text( ) ).
ENDIF.
ENDLOOP.
Variant 2 - CASE TYPE
In the last example, the simplest variant so far, as it is easy to overview and saves unnecessary code. We use the CASE TYP statement. This statement is an extension of the classic CASE and can also create types. In WHEN we query the corresponding interface and assign it to a specific variable.
CASE TYPE OF ls_message-item.
WHEN TYPE if_bali_message_getter INTO DATA(lo_message).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
ENDCASE.
Now we call the conversion in the loop and get the same result as with the other variants. This variant is even shorter and a little clearer. You will no longer find a CAST either, as this is taken over by the CASE TYPE.
LOOP AT it_messages INTO DATA(ls_message).
io_out->write( `-` ).
CASE TYPE OF ls_message-item.
WHEN TYPE if_bali_message_getter INTO DATA(lo_message).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
WHEN TYPE if_bali_free_text_getter INTO DATA(lo_free_text).
io_out->write( lo_free_text->get_message_text( ) ).
WHEN TYPE if_bali_exception_getter INTO DATA(lo_exception).
io_out->write( lo_exception->exception_class ).
io_out->write( lo_exception->exception_id_name ).
io_out->write( lo_exception->get_message_text( ) ).
ENDCASE.
ENDLOOP.
Full example
Here is the complete class from the various examples. You will probably have to adjust the handle object if you want to run it on your system, as your system has its own ID created.
CLASS zcl_bs_demo_type_casting DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PRIVATE SECTION.
CONSTANTS c_handle TYPE if_bali_log_db=>ty_handle VALUE '<YOUR_HANDLE>'.
METHODS classic_casting
IMPORTING it_messages TYPE if_bali_log=>ty_item_table
io_out TYPE REF TO if_oo_adt_classrun_out.
METHODS if_casting
IMPORTING it_messages TYPE if_bali_log=>ty_item_table
io_out TYPE REF TO if_oo_adt_classrun_out.
METHODS case_casting
IMPORTING it_messages TYPE if_bali_log=>ty_item_table
io_out TYPE REF TO if_oo_adt_classrun_out.
ENDCLASS.
CLASS zcl_bs_demo_type_casting IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
DATA(lt_messages) = cl_bali_log_db=>get_instance( )->load_log( c_handle )->get_all_items( ).
out->write( `--> CLASSIC` ).
classic_casting( it_messages = lt_messages
io_out = out ).
out->write( |-| ).
out->write( `--> IS INSTANCE` ).
if_casting( it_messages = lt_messages
io_out = out ).
out->write( |-| ).
out->write( `--> CASE TYPE` ).
case_casting( it_messages = lt_messages
io_out = out ).
ENDMETHOD.
METHOD classic_casting.
LOOP AT it_messages INTO DATA(ls_message).
io_out->write( `-` ).
TRY.
DATA(lo_message) = CAST if_bali_message_getter( ls_message-item ).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
CONTINUE.
CATCH cx_sy_move_cast_error.
ENDTRY.
TRY.
DATA(lo_free_text) = CAST if_bali_free_text_getter( ls_message-item ).
io_out->write( lo_free_text->get_message_text( ) ).
CONTINUE.
CATCH cx_sy_move_cast_error.
ENDTRY.
TRY.
DATA(lo_exception) = CAST if_bali_exception_getter( ls_message-item ).
io_out->write( lo_exception->exception_class ).
io_out->write( lo_exception->exception_id_name ).
io_out->write( lo_exception->get_message_text( ) ).
CONTINUE.
CATCH cx_sy_move_cast_error.
ENDTRY.
ENDLOOP.
ENDMETHOD.
METHOD if_casting.
LOOP AT it_messages INTO DATA(ls_message).
io_out->write( `-` ).
IF ls_message-item IS INSTANCE OF if_bali_message_getter.
DATA(lo_message) = CAST if_bali_message_getter( ls_message-item ).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
ELSEIF ls_message-item IS INSTANCE OF if_bali_free_text_getter.
DATA(lo_free_text) = CAST if_bali_free_text_getter( ls_message-item ).
io_out->write( lo_free_text->get_message_text( ) ).
ELSEIF ls_message-item IS INSTANCE OF if_bali_exception_getter.
DATA(lo_exception) = CAST if_bali_exception_getter( ls_message-item ).
io_out->write( lo_exception->exception_class ).
io_out->write( lo_exception->exception_id_name ).
io_out->write( lo_exception->get_message_text( ) ).
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD case_casting.
LOOP AT it_messages INTO DATA(ls_message).
io_out->write( `-` ).
CASE TYPE OF ls_message-item.
WHEN TYPE if_bali_message_getter INTO DATA(lo_message).
io_out->write( lo_message->id ).
io_out->write( lo_message->number ).
io_out->write( lo_message->variable_1 ).
io_out->write( lo_message->get_message_text( ) ).
WHEN TYPE if_bali_free_text_getter INTO DATA(lo_free_text).
io_out->write( lo_free_text->get_message_text( ) ).
WHEN TYPE if_bali_exception_getter INTO DATA(lo_exception).
io_out->write( lo_exception->exception_class ).
io_out->write( lo_exception->exception_id_name ).
io_out->write( lo_exception->get_message_text( ) ).
ENDCASE.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
Availability
From which release are the new statements available? You can find the relevant information in the ABAP Feature Matrix (AFM):
- IS INSTANCE OF - S/4 HANA 1511 (7.50)
- CASE TYPE OF - S/4 HANA 1511 (7.50)
Conclusion
In the object-oriented ABAP world, casting objects is standard in order to obtain the necessary information in processing and to manage objects generically in your table. We hope that with the help of this article you can now master this better.