ABAP Cloud - JSON Conversion
Is there a new API for converting JSON and do you need it for ABAP Cloud? Here we will take a look at the structure and conversion of JSON.
Table of contents
How is JSON structured and how can you easily work with it in ABAP? In this article, we will look at these and various other questions. We will look at the format and its use in the ABAP area.
Introduction
JSON is now the standard for processing and providing data when using REST-based interfaces. OData v4 also relies entirely on the JSON format because it is lightweight and, unlike XML, does not require as much memory. JSON formats can be converted into data objects quite easily and are easy to read, which makes it the tool of choice.
Basics
How is a JSON actually structured and how can you easily read it and convert it into ABAP structures? In this section we will look at the different elements.
Structure
Let's first look at a first example. We have constructed a JSON that contains different sections.
{
"text": "My text",
"number_integer": 37,
"number_decimal": 10.12,
"boolean": true,
"array_element": [
{
"text2": "A",
"number2": 1
},
{
"text2": "B",
"number2": 2
},
{
"text2": "C",
"number2": 3
}
],
"array_data": [
"A-A",
"A-B",
"B-A"
],
"dynamic_list": {
"AED": 12.50,
"EUR": 5.20,
"USD": 9.96
}
}
The following facts apply:
- { } is an object; this element is comparable to a structure in ABAP. Within the object there are various fields that you can address using the name. Each name must be unique (key).
- [ ] is an array, i.e. a table in ABAP. An array can be simple, such as the node "array_data", or it can have a structure in the form of objects ("array_element").
- The name of a field is delimited by " " ", the data type is described by the spelling.
- String - "A text as a value"
- Number - 5.20 or 12
- Boolean - true or false
- There is no comma after the last element of an array or object.
JSON structure
When we convert data from JSON to ABAP, we have the option of working with the data dynamically and using references. However, we can also create a local structure to map the data directly into an ABAP structure and thus be able to access it easily. You can display and prepare a JSON using an online tool such as the JSON Editor Online. Especially when formatting isn't that easy, the tool does it for you and you can read the JSON more easily. So let's start by building the structure from the outside in and in ABAP from the bottom up:
Since the outer part is an object { } , we start by creating a structure and can adopt the various elements. For DECIMAL, we create a local data type and adopt the fields of the structure 1:1 from the data. For the arrays [ ] we create table types that we will generate later, for dynamic_list we set a reference because the element does not have a fixed structure, but rather the mapping currency to amount.
TYPES td_decimal TYPE p LENGTH 16 DECIMALS 2.
TYPES: BEGIN OF ts_dummy_data,
text TYPE string,
number_integer TYPE i,
number_decimal TYPE td_decimal,
boolean TYPE abap_bool,
array_element TYPE tt_element,
array_data TYPE tt_data,
dynamic_list TYPE REF TO data,
END OF ts_dummy_data.
Now let's create the next element, in this case a table that consists of objects, i.e. structures.
Since the object looks "stable" so far, we can create a structure for it that has two fields. To do this, we also create the table type that we already used in our data structure. The definition then looks like this:
TYPES: BEGIN OF ts_element,
text2 TYPE string,
number2 TYPE i,
END OF ts_element.
TYPES tt_element TYPE STANDARD TABLE OF ts_element WITH EMPTY KEY.
The JSON is an array [ ], but since it does not contain an object { }, it is a table with a simple data type.
First of all, we generate a simple data type. If we are sure that the values are short, we can use CHAR with an appropriate length directly, otherwise we would use String.
TYPES td_char TYPE c LENGTH 10.
TYPES tt_data TYPE STANDARD TABLE OF td_char WITH EMPTY KEY.
The final type of the JSON now looks like this, but you have to pay attention to the order in which you create the individual types:
TYPES td_decimal TYPE p LENGTH 16 DECIMALS 2.
TYPES td_char TYPE c LENGTH 10.
TYPES: BEGIN OF ts_element,
text2 TYPE string,
number2 TYPE i,
END OF ts_element.
TYPES tt_element TYPE STANDARD TABLE OF ts_element WITH EMPTY KEY.
TYPES tt_data TYPE STANDARD TABLE OF td_char WITH EMPTY KEY.
TYPES: BEGIN OF ts_dummy_data,
text TYPE string,
number_integer TYPE i,
number_decimal TYPE td_decimal,
boolean TYPE abap_bool,
array_element TYPE tt_element,
array_data TYPE tt_data,
dynamic_list TYPE REF TO data,
END OF ts_dummy_data.
ABAP structure
For the conversion from ABAP to JSON, you also need a structure with different types in order to generate JSON from ABAP. To do this, we create a simple structure that will generate an object.
TYPES: BEGIN OF ts_internal,
camel_string TYPE string,
singlestring TYPE string,
camel_number TYPE td_decimal,
singlenumber TYPE td_decimal,
camel_int TYPE i,
singleint TYPE i,
camel_bool TYPE abap_bool,
singlebool TYPE abap_bool,
END OF ts_internal.
TYPES tt_internal TYPE STANDARD TABLE OF ts_internal WITH EMPTY KEY.
Class /UI2/CL_JSON
In this section we look at the class /UI2/CL_JSON, which is already available in older releases and provides a wide range of functions.
JSON to ABAP
In this example we want to convert the data from JSON to the ABAP structure. To do this we use the class's DESERIALIZE method:
To do this we first define our local target structure and then generate the JSON in the form of a string. Finally, we call the method and convert the data. We can pass a string (readable) via JSON and an XString (binary) via JSONX.
DATA ls_dummy TYPE ts_dummy_data.
DATA(ld_json) = get_dummy_json( ).
/ui2/cl_json=>deserialize( EXPORTING json = ld_json
CHANGING data = ls_dummy ).
After the conversion has been carried out, we look at the result in the debugger. The data has been cleaned and assigned. The reference is also filled and is ready for processing. In principle, you do not have to map all the data. If you omit fields or structures, they are ignored and do not lead to an error.
ABAP to JSON
In this example, we now want to convert our internal data to JSON and try out the various options of the class. In the first example, we therefore use the simplest case and call the SERIALIZE method without filling in any further parameters.
DATA(ls_internal) = get_internal_data( ).
DATA(ld_json) = /ui2/cl_json=>serialize( data = ls_internal ).
The output is accordingly flat and practical, all fields are created in uppercase letters and with underscores.
In the next example we use the FORMAT_OUTPUT parameter, so we can format the output in a readable way, especially if we want to write the data to the console or a file.
ld_json = /ui2/cl_json=>serialize( data = ls_internal
format_output = abap_true ).
The output of the data now looks like this. By using the correct data type, the boolean value is also converted cleanly. The conversion does not currently work with ABAP_BOOLEAN.
We can control the output of the fields using the PRETTY_NAME parameter:
- /UI2/CL_JSON=>PRETTY_MODE-LOW_CASE - Instead of being completely uppercase, the field names are now written in lowercase.
- /UI2/CL_JSON=>PRETTY_MODE-CAMEL_CASE - The first letter remains lowercase, the first letter after an underscore is uppercase; and the underscore is removed.
Class XCO_CP_JSON
The XCO classes are still quite new in ABAP development and are the new interfaces for all types of objects in the standard. For the conversion we therefore access the class XCO_CP_JSON, which was designed for accessing JSON.
JSON to ABAP
To convert the data we first call the FROM_STRING method to pass the JSON string. Then we pass a transformation so that the boolean value is converted to ABAP-BOOL. We write the data into our local structure using the WRITE_TO method.
DATA ls_dummy TYPE ts_dummy_data.
DATA(ld_json) = get_dummy_json( ).
xco_cp_json=>data->from_string( ld_json )->apply( VALUE #( ( xco_cp_json=>transformation->boolean_to_abap_bool ) ) )->write_to(
REF #( ls_dummy ) ).
Hint: Currently, the conversion only works if we remove the dynamic structure/reference from the data type, otherwise the runtime error XML_FORMAT_ERROR occurs. This means that the dynamic conversion does not seem to work.
ABAP to JSON
How can we generate a JSON string with the new class? To do this, we call the DATA object via the class and pass the data using the FROM_ABAP method. We can then generate a JSON string using the TO_STRING method.
DATA(lt_internal) = get_internal_data( ).
DATA(ld_json) = xco_cp_json=>data->from_abap( lt_internal )->to_string( ).
If we want to use additional formatters, we can do this using the APPLY method. Here is a small example where we add a formatter:
ld_json = xco_cp_json=>data->from_abap( lt_internal )->apply(
VALUE #( ( xco_cp_json=>transformation->underscore_to_pascal_case ) )
)->to_string( ).
The following formatting options are available to us:
- XCO_CP_JSON=>TRANSFORMATION->UNDERSCORE_TO_PASCAL_CASE - First letter is converted to uppercase, as are letters after an underscore. The underscore is always removed.
- XCO_CP_JSON=>TRANSFORMATION->UNDERSCORE_TO_CAMEL_CASE - First letter remains lowercase, letters after an underscore are uppercase; converted and the underscore removed.
Hint: Converting ABAP_BOOL to a Boolean value was not possible until now. The values of ABAP_TRUE and ABAP_FALSE are transferred to the field as a string.
Dynamic data
When converting from JSON to ABAP, we also had a dynamic table. How can we map this cleanly and access the values? Basically, the reference consists of a structure that dynamically has many columns, where the column name contains the information about the currency and the value is the content. To do this, we first need the information about the components. To do this, we assign the reference to a field symbol and get a description of the structure via the CL_ABAP_TYPEDESCR class.
ASSIGN ls_dummy-dynamic_list->* TO FIELD-SYMBOL(<ls_dynamic>).
DATA(lo_description) = CAST cl_abap_structdescr( cl_abap_typedescr=>describe_by_data( <ls_dynamic> ) ).
Now we can loop over the individual components of the structure and assign the value from the structure to a field symbol (<ld_value>). With the name and
LOOP AT lo_description->components ASSIGNING FIELD-SYMBOL(<ls_componenent>).
ASSIGN COMPONENT <ls_componenent>-name OF STRUCTURE <ls_dynamic> TO FIELD-SYMBOL(<ld_value>).
ENDLOOP.
After converting the value to our target data type, we output the result to the console. This allows us to map the dynamic types. In principle, however, we recommend that you map the type directly to ABAP types if possible, as this saves you work and time.
Complete example
Here you can find the complete class again with all the examples shown and the corresponding output. You can also use the class to debug the output and information.
CLASS zcl_bs_demo_json DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PRIVATE SECTION.
TYPES td_decimal TYPE p LENGTH 16 DECIMALS 2.
TYPES td_char TYPE c LENGTH 10.
TYPES: BEGIN OF ts_element,
text2 TYPE string,
number2 TYPE i,
END OF ts_element.
TYPES tt_element TYPE STANDARD TABLE OF ts_element WITH EMPTY KEY.
TYPES tt_data TYPE STANDARD TABLE OF td_char WITH EMPTY KEY.
TYPES: BEGIN OF ts_dummy_data,
text TYPE string,
number_integer TYPE i,
number_decimal TYPE td_decimal,
boolean TYPE abap_bool,
array_element TYPE tt_element,
array_data TYPE tt_data,
dynamic_list TYPE REF TO data,
END OF ts_dummy_data.
TYPES: BEGIN OF ts_internal,
camel_string TYPE string,
singlestring TYPE string,
camel_number TYPE td_decimal,
singlenumber TYPE td_decimal,
camel_int TYPE i,
singleint TYPE i,
camel_bool TYPE abap_bool,
singlebool TYPE abap_bool,
END OF ts_internal.
TYPES tt_internal TYPE STANDARD TABLE OF ts_internal WITH EMPTY KEY.
METHODS get_dummy_json
RETURNING VALUE(rd_result) TYPE string.
METHODS get_internal_data
RETURNING VALUE(rt_result) TYPE tt_internal.
METHODS convert_json_with_ui2
IMPORTING io_out TYPE REF TO if_oo_adt_classrun_out.
METHODS convert_json_with_xco
IMPORTING io_out TYPE REF TO if_oo_adt_classrun_out.
METHODS convert_int_to_json_with_ui2
IMPORTING io_out TYPE REF TO if_oo_adt_classrun_out.
METHODS convert_dynamic_data
IMPORTING io_out TYPE REF TO if_oo_adt_classrun_out.
METHODS convert_int_to_json_with_xco
IMPORTING io_out TYPE REF TO if_oo_adt_classrun_out.
ENDCLASS.
CLASS zcl_bs_demo_json IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
convert_json_with_ui2( out ).
convert_json_with_xco( out ).
convert_int_to_json_with_ui2( out ).
convert_int_to_json_with_xco( out ).
convert_dynamic_data( out ).
ENDMETHOD.
METHOD get_dummy_json.
rd_result = |{| &&
| "text": "My text",| &&
| "number_integer": 37,| &&
| "number_decimal": 10.12,| &&
| "boolean": true,| &&
| "array_element": [| &&
| {| &&
| "text2": "A",| &&
| "number2": 1| &&
| },| &&
| {| &&
| "text2": "B",| &&
| "number2": 2| &&
| },| &&
| {| &&
| "text2": "C",| &&
| "number2": 3| &&
| }| &&
| ],| &&
| "array_data": [| &&
| "A-A",| &&
| "A-B",| &&
| "B-A"| &&
| ],| &&
| "dynamic_list": {| &&
| "AED": 12.50,| &&
| "EUR": 5.20,| &&
| "USD": 9.96| &&
| }| &&
|}|.
ENDMETHOD.
METHOD get_internal_data.
rt_result = VALUE #( ( camel_string = 'String 1'
singlestring = 'String 2'
camel_number = '9.95'
singlenumber = '5.59'
camel_int = 2
singleint = 12
camel_bool = abap_true
singlebool = abap_false ) ).
ENDMETHOD.
METHOD convert_json_with_ui2.
DATA ls_dummy TYPE ts_dummy_data.
DATA(ld_json) = get_dummy_json( ).
/ui2/cl_json=>deserialize( EXPORTING json = ld_json
CHANGING data = ls_dummy ).
ENDMETHOD.
METHOD convert_json_with_xco.
DATA ls_dummy TYPE ts_dummy_data.
DATA(ld_json) = get_dummy_json( ).
xco_cp_json=>data->from_string( ld_json )->apply( VALUE #( ( xco_cp_json=>transformation->boolean_to_abap_bool ) ) )->write_to(
REF #( ls_dummy ) ).
ENDMETHOD.
METHOD convert_int_to_json_with_ui2.
DATA(ls_internal) = get_internal_data( ).
DATA(ld_json) = /ui2/cl_json=>serialize( data = ls_internal ).
io_out->write( `Build raw:` ).
io_out->write( ld_json ).
ld_json = /ui2/cl_json=>serialize( data = ls_internal
format_output = abap_true ).
io_out->write( `Format output:` ).
io_out->write( ld_json ).
ld_json = /ui2/cl_json=>serialize( data = ls_internal
pretty_name = /ui2/cl_json=>pretty_mode-low_case
format_output = abap_true ).
io_out->write( `Pretty low-case:` ).
io_out->write( ld_json ).
ld_json = /ui2/cl_json=>serialize( data = ls_internal
pretty_name = /ui2/cl_json=>pretty_mode-camel_case
format_output = abap_true ).
io_out->write( `Pretty camel-case:` ).
io_out->write( ld_json ).
ld_json = /ui2/cl_json=>serialize( data = ls_internal
conversion_exits = abap_true ).
io_out->write( `Test:` ).
io_out->write( ld_json ).
ENDMETHOD.
METHOD convert_dynamic_data.
DATA ls_dummy TYPE ts_dummy_data.
DATA(ld_json) = get_dummy_json( ).
/ui2/cl_json=>deserialize( EXPORTING json = ld_json
CHANGING data = ls_dummy ).
ASSIGN ls_dummy-dynamic_list->* TO FIELD-SYMBOL(<ls_dynamic>).
DATA(lo_description) = CAST cl_abap_structdescr( cl_abap_typedescr=>describe_by_data( <ls_dynamic> ) ).
LOOP AT lo_description->components ASSIGNING FIELD-SYMBOL(<ls_componenent>).
ASSIGN COMPONENT <ls_componenent>-name OF STRUCTURE <ls_dynamic> TO FIELD-SYMBOL(<ld_value>).
io_out->write( |Currency: { <ls_componenent>-name } with value: { CONV td_decimal( <ld_value>->* ) }| ).
ENDLOOP.
ENDMETHOD.
METHOD convert_int_to_json_with_xco.
DATA(lt_internal) = get_internal_data( ).
DATA(ld_json) = xco_cp_json=>data->from_abap( lt_internal )->to_string( ).
io_out->write( `Raw output:` ).
io_out->write( ld_json ).
ld_json = xco_cp_json=>data->from_abap( lt_internal )->apply(
VALUE #( ( xco_cp_json=>transformation->underscore_to_pascal_case ) )
)->to_string( ).
io_out->write( `Apply underscore_to_pascal_case:` ).
io_out->write( ld_json ).
ld_json = xco_cp_json=>data->from_abap( lt_internal )->apply(
VALUE #( ( xco_cp_json=>transformation->underscore_to_camel_case ) )
)->to_string( ).
io_out->write( `Apply underscore_to_camel_case:` ).
io_out->write( ld_json ).
ENDMETHOD.
ENDCLASS.
ABAP Cloud
There are currently two different released classes for ABAP Cloud, the classic /UI2/CL_JSON and the new XCO Library XCO_CP_JSON. Here you can find a summary of the two classes and a comparison of the features:
Feature | /UI2/CL_JSON | XCO_CP_JSON |
---|---|---|
ABAP_BOOL -> BOOL | ✔️ | |
BOOL -> ABAP_BOOL | ✔️ | ✔️ |
Formatter UPPER_FORMAT | ✔️ | ✔️ |
Formatter lower_format | ✔️ | |
Formatter camelCase | ✔️ | ✔️ |
Formatter PascalCase | ✔️ | |
Dynamic data | ✔️ | |
Output Formatter | ✔️ | |
Additional field mapping | ✔️ |
Except for a missing formatter, we still recommend using the /UI2/CL_JSON class if it meets your requirements.
Availability
Currently, the class /UI2/CL_JSON is only available in the ABAP environment and the public cloud with C1 contract. However, there is already an assurance for S/4HANA 2023 FPS2 that the class will be delivered. Currently, you have two options for using it:
- Creating a wrapper
- Setting the C1 contract on the class (modification)
We are currently discussing whether advance delivery by notice is possible.
Conclusion
In this article we wanted to give you a better understanding of working with JSON in ABAP. You should now understand the structure more easily and be able to map it in ABAP. With the two classes you can convert to internal structures and thus implement HTTP interfaces and process their data.
More information:
GitHub - ABAP to JSON (under documentation)
SAP BTP - JSON