Professional Documents
Culture Documents
Krmindevej 3
3500 Vrlse
Danmark
Side 1 af 47
You can use this solution as inspiration for other types of issues, where you need
to work with dynamic structured complex data,
The class support the JSON format entirely, as describe at http://www.JSON.org
with very few exceptions.
Introduction to JSON
JSON or JavaScript Object Notation, is a lightweight text-based open standard designed for
human-readable data interchange. It is derived from the JavaScript scripting language for
representing simple data structures and associative arrays, called objects. Despite its
relationship to JavaScript, it is language-independent, with parsers available for many
languages.
The official internet media type for JSON is application/JSON. The JSON filename extension
is .JSON.
The JSON format is often used for serializing and transmitting structured data over a network
connection. It is used primarily to transmit data between a server and web application,
serving as an alternative to XML.
For more information see http://www.JSON.org or other websites.
JSON Validator
When working with interpreter or compilers like the JSON parser for ABAP, it's important to
have a tool that you trust and that can check the JSON syntax. Several web services offer
syntax checkers like http://JSONlint.com which is the one I prefer.
Using a JSON validator, you are able to play around with JSON before after
serialize/deserialize checking the results from the serialize method and ensure the right
syntax for your input to the deserializer method.
Udskrevet: 05-09-2013
Side 2 af 47
The JSONLint Validator is a simple web editor where you fill in your JSON script, hit the
Validate button and get the result: valid JSON or a list of errors in the script.
Side 3 af 47
Object
In JSON an object is an unordered set of name/value pairs. An object begins with { (left brace)
and ends with } (right brace). Each name is followed by : (colon) and the name/value pairs are
separated by , (comma).
The equivalent data structure in ABAP, is a data structure comprising one or more fields. A
data structure is usually fixed, defined in the program or in the data dictionary. It is possible
to dynamically define the data structure in your program, but it means that the transmitter of a
JSON object, must send meta data describing the data structure. Metadata is not part of this
JSON solution, and thus the data structure used, is fixed in ABAP or Data dictionary.
If using a single field also called scalar field, it does not matter which field name is specified
as field name is not used for scalar fields in inbound mode, but in outbound mode you or at
least the web developer would like you to provide relevant fieldname, therefore the serialize
method provide the possibility to pass the object name. If you do not pass an object name the
method will name the scalar field after it's type.
For structures the fieldnames within ABAP must be the same as used in JSON,
otherwise you get a format error message. The deserialize function depend on the
fieldname. The JSON class however support the use of simple variables (scalar
fields) instead of structures, if and only if you only send one variable.
If you need to facilitate a collection of scalar fields, you must create a data structure
with the scalar fields in ABAP.
<<LISTING 1 >>
Listing 1: Code example for simple data structure
TYPES:
BEGIN OF ty_sflight
,mandt
TYPE
,carrid
TYPE
,connid
TYPE
,fldate
TYPE
,price
TYPE
,currency
TYPE
,planetype
TYPE
,seatsmax
TYPE
,seatsocc
TYPE
,paymentsum
TYPE
Udskrevet: 05-09-2013
s_mandt
s_carr_id
s_conn_id
s_date
s_price
s_currcode
s_planetye
s_seatsmax
s_seatsocc
s_sum
Side 4 af 47
,seatsmax_b
,seatsocc_b
,seatsmax_f
,seatsocc_f
,END OF ty_sflight.
DATA:
JSON_data
,ABAP_data
TYPE
TYPE
TYPE
TYPE
s_smax_b
s_socc_b
s_smax_f
s_socc_f
TYPE string
TYPE ty_sflight.
<</LISTING 1>>
Udskrevet: 05-09-2013
Side 5 af 47
Here we use local defined data structure as the object, we read some data from the
database into the internal ABAP data structure and calls the serialized( ) method.
This method returns the JSON data. The next step is to clear the ABAP data and to
deserialize from JSON to ABAP data.
For the JSON we want to name our data structure as "MyData", this is an optional
parameter. If we did not added this optional parameter, the data structure was
nameless.
The ABAP data is converted into the JSON format and checked in JSONLint
Validator.
<<LISTING 2 >>
Listing 2: JSON after serialize and without optional iv_object_name
{
"mandt": "000",
"carrid": "AA",
"connid": "0017",
"fldate": "20110427",
"price": "422.94 ",
"currency": "USD",
"planetype": "747-400",
"seatsmax": "385 ",
"seatsocc": "365 ",
"paymentsum": "188462.26 ",
"seatsmax_b": "31 ",
"seatsocc_b": "30 ",
"seatsmax_f": "21 ",
"seatsocc_f": "18 "
}
<</LISTING 2>>
As we are working with a data structure, the serialize is able to determine the correct
Udskrevet: 05-09-2013
Side 6 af 47
field name, both from local and global defined structures. For a scalar field we can't
determine the fieldname, therefore the serialize support the client for naming scarlar
fields. But you can give the aray or the object a name in JSON.
<<LISTING 3 >>
Listing 3: JSON after serialize and with optional iv_object_name
{
"MyData": {
"mandt": "000",
"carrid": "AA",
"connid": "0017",
"fldate": "20110427",
"price": "422.94 ",
"currency": "USD",
"planetype": "747-400",
"seatsmax": "385 ",
"seatsocc": "365 ",
"paymentsum": "188462.26 ",
"seatsmax_b": "31 ",
"seatsocc_b": "30 ",
"seatsmax_f": "21 ",
"seatsocc_f": "18 "
}
}
<</LISTING 3>>
For the serialize( ) method, the client can specify a object name for the scalar field,
structure or table at the topmost level. For deserialize the client can specify an object
name for the scalar field, structure or table at the topmost level. If the JSON does
have a named structure and the ABAP data point to a data structure without the
component with this name, then the client must add the parameter iv_object_name.
For deserialize the client must use a data structure in SAP similar to the JSON data
structure, but as described above the method does support IV_OBJECT_NAME.
The object can be a single scalar field or a structure of one to many fields.
For scalar field the IV_OBJECT_NAME parameter is mandatory for serialize, but
optional for deserialize.
<<LISTING 4 >>
Listing 4: Code example scalar field
DATA:
ABAP_data
,JSON_data
* select
SELECT
WHERE
AND
AND
TYPE s_sum
TYPE string.
Side 7 af 47
<</LISTING 4>>
<<LISTING 5 >>
Listing 4: Scalar field as JSON
{
"Expences": "188462.26 "
}
<</LISTING 5>>
JSON
{
"field1": "First Value",
"field2": "second Value",
"field3": "third value"
}
ABAP
List of Scarlar fields that are not part of a structure, is not
supported in ABAP. But you just create a fixed local structure to
solve the need of a list of scalar fields. Do you need the list to
be dynamic you either dynamic build the data type in ABAP
depending on the JSON input or you ask the client to deliver
dynamic fields as table entries.
}
{
}
{
}
{
}
{"anyname":"611"}
Udskrevet: 05-09-2013
Side 8 af 47
Array
In JSON An array is an ordered collection of values. An array begins with [ (left bracket) and
ends with ] (right bracket). Values are separated by , (comma).
In ABAP we do not have arrays as in other languages, we do have however what we call
internal tables of different kinds sorted, hashed, index, standard and any tables. The tables
within ABAP can be table of fields of the same data element or data type or it could be tables
of complex structures. In JSON value could be string, number, object, array, true, false and
null. The object and array type has similarities with table of complex structures, whereas the
other have similarities with data elements or data type. True, False and null does not exists in
ABAP runtime, but we could build new data elements.
We could look at the simple type and argue that they are face less, we don't have their name
in JSON. In JSON an array could include various values of different data type, that is not the
case within ABAP, but a workaround where we use data type any or string could be used. The
Array in JSON could be a simple value table, as we in ABAP do not know the fieldname, we
can't know the correct data type, but if sender and receiver have an agreement, that the data
have fixed position, then we could used simple value tables.
examples:
["1922","1934","1962","1977","1998"]
[
{"Name":"Patrick","Birth":"1934","Month":"11"},
{"Name":"Carl","Birth":"1964","Month":"9"},
{"Name":"Jonas","Birth":"1988","Month":"3"},
{"Name":"Mogens","Birth":"1955","Month":"10"}
]
["611"]
[ { "F": 23 } ]
Side 9 af 47
<<LISTING 6 >>
Listing 6: Code example for table of simple data structure
DATA:
li_conditions
,lst_condition
,JSON_data
.
<</LISTING 6>>
Udskrevet: 05-09-2013
Side 10 af 47
<<LISTING 7 >>
Listing 7: JSON Result
[
{
"mandt": "000",
"procid": "M01",
"condid": "C017",
"bis": "20100620",
"cond_class": "ZCL_MOD_PE_PROCESS_EXE",
"cond_method": "DUMMY_CONDITION",
"dummy_condition": ""
},
....
{
"mandt": "000",
"procid": "P93",
"condid": "C028",
"bis": "99991231",
"cond_class": "ZCL_MOD_PE_P93",
"cond_method": "DUMMY_CONDITION",
"dummy_condition": ""
}
]
<</LISTING 7>>
The JSON list continues over several entries, here you only see the first and last.
Side 11 af 47
<</LISTING 8>>
<<LISTING 9 >>
Listing 9: Result of Array example
[
"BPEL",
"D01",
"D02",
"L01",
...
"X01",
"X01"
]
<</LISTING 9>>
Udskrevet: 05-09-2013
Side 12 af 47
Value
A value can be a string in double quotes, a number, true, false, null, an object or an
Udskrevet: 05-09-2013
Side 13 af 47
[
365
]
[
true
]
[
false
]
[
null
]
[
null,
"first string,
Udskrevet: 05-09-2013
1234,
true,
false,
"second string"
]
[
null,
"first string",
1234,
true,
{
"Name": "JakeSmith",
"Age": "55",
"Sex": "Male"
},
false,
"secondstring"
]
<<LISTING 10 >>
Listing 10: Code example for mixed data types as values
DATA:
JSON_data
,ABAP_data
,ABAP_table
TYPE string
TYPE string
TYPE STANDARD TABLE OF string.
<</LISTING 10>>
<<LISTING 11 >>
Listing 11: Code example for mixed data types as integer
DATA:
JSON_data
,ABAP_data
,ABAP_table
TYPE string
TYPE i
TYPE STANDARD TABLE OF i.
FIELD-SYMBOLS:
Udskrevet: 05-09-2013
Side 15 af 47
<ABAP>
* initialize
APPEND 233
APPEND 234
APPEND 235
TYPE ANY.
TO ABAP_table.
TO ABAP_table.
TO ABAP_table.
<</LISTING 11>>
Udskrevet: 05-09-2013
Side 16 af 47
String
A string is a sequence of zero or more Unicode characters, wrapped in double quotes, using
backslash escapes. A character is represented as a single character string. A string is very
much like a C or Java string.
The representation of strings is similar to conventions used in the C family of programming
languages. A string begins and ends with quotation marks. All Unicode characters may be
placed within the quotation marks except for the characters that must be escaped: quotation
mark, reverse solidus, and the control characters (U+0000 through U+001F).
Any character may be escaped. It is a requirement of the JSON spec that all data use a
Unicode encoding. In the ABAP Parser the unicode check is set active.
in ABAP you do not deal with these special control characters, that is something that is take
care of by your own framework og standard communication tool. The ABAP parser will
however strip any of these control characters.
Only the sequence of numbers, letters and special characters wrapped in double quotes, will
be passed to ABAP, and as we do not have these control characters in ABAP, no such control
value is parsed from ABAP to JSON.
Udskrevet: 05-09-2013
Side 17 af 47
Examples:
{ "simple_string": "Anders Mattesen" }
{"date": "20131215"}
{"control": "\""}
Number
A number is very much like a C or Java number, except that the octal and hexadecimal
formats are not used.
Whitespace can be inserted between any pair of tokens. Excepting a few encoding details,
that completely describes the language.
ABAP can convert '1.0055000075E8 ' to numeric if the data type is float, og deserialize does
support float.
{"num":"0002277891"}
{"packed":"11223377 "}
{"noname": 23.45 }
{"noname": -76.42 }
{"noname": 1.0055000075E+8 }
Side 18 af 47
Side 19 af 47
The class has three static and public methods that facilitates the singleton pattern:
SERIALIZE( ),
DESERIALIZE( ),
GET_INSTANCES( ).
When the client needs an instance of this class one of these three methods must be called. All
three using the input parameter ABAP data and JSON data, where the JSON data is of type
string and the ABAP data type data, meaning variable of any internal ABAP type. The three
methods then should dynamic investigate the actual type of data, which can be structured or
deep structured data, tables or table types, even arrays and simple plain fields. The
prerequisite is that we always have only one parameter that should be processed, however
that is not the fact in real life as we often have to deal with multiple parameters, but that is
easy to solve later by building dynamic structures at runtime. You could also use the method
COMPILE_JSON that merge multiple JSON objects into one JSON object.
One prerequisites is that the ABAP developer and WEB developer must make some kind of
agreement or contract, about what kind of data they want to exchange. ABAP does not
support de automatic declaration of data depending on contents as in java, JavaScript and
other languages. We need to know the data type.
The naming convention used in the class follows the same naming convention as in SAP
BRF+.
To be able to effective process generic and dynamic data and data structures, the ABAP
language provide these commands and build-in classes, that is used by the class and that you,
as a developer need to have some knowledge about:
Udskrevet: 05-09-2013
Side 20 af 47
As ABAP developer you should read the documentation and look into these commands and
classes. They are essential to generic- and dynamic programming.
Properties
Figur 12 Properties
It's important to set instantiation to private, this way you can control the instantiation through
factory methods, which by the way controls that the class follows the singleton design
pattern, meaning that only one instance of this class exists. It's up to you, to decide if the
class should be final or not. In your own productive version, you should assign a message
class and program status and introduce some error handling features.
As this class supports singleton design patterns, the instantiation is private. The
client has to call one of the three factory methods:
SERIALIZE( ),
DESERIALZE(),
GET_INSTANCE().
Attributes
Before looking at the methods, I would like to discuss the attributes. As principle only
Udskrevet: 05-09-2013
Side 21 af 47
use private attributes to ensure that only the class own methods can change
attributes. Avoid to have unnecessary attributes.
Figur 13 Attributes
The attribute GO_OBJECT is the most interesting attribute, as it contain the instance of the
class, which is used by the factory method, to check if the class has already been instantiated.
The GT_FRAGMENTS is a internal table of fragments of the JSON code, the
GR_ABAP_REF is the data reference to any ABAP data type and the GR_JSON_REF is a
data reference to the JSON string. The rest of the attributes is just some constants.
Methods
Figur 14 Methods
The class contains the a number of methods and in the following the methods is described in
details.
+CLASS_CONSTRUCTOR( ): Static/public.
Public Static method that creates variables with constant values like <space> value <space>
used later for JSON formatting. Could have been done by using constants, but easier to make
Udskrevet: 05-09-2013
Side 22 af 47
enhancements. The class constructor is initiated at the first the client references the class .
This is similar to initialization. As this method is the class constructor no parameters are
possible.
<<LISTING 12>>
Listing 12: CLASS CONSTRUCTOR( )
*--------------------------------------------------------------------*
* Create variables with constant values like <space> value <space>
*
* used later for JSON format.
*
*--------------------------------------------------------------------*
method class_constructor.
cl_ABAP_string_utilities=>c2str_preserving_blanks(
exporting source = ':'
importing dest
= c_colon ) .
cl_ABAP_string_utilities=>c2str_preserving_blanks(
exporting source = ','
importing dest
= c_comma ) .
cl_ABAP_string_utilities=>c2str_preserving_blanks(
exporting source = '"'
importing dest
= c_quotes ) .
endmethod.
<</LISTING 12>>
Public method with the only purpose to create the data references to ABAP and JSON data
and save these as instance attributes like, "me->gr_ABAP_ref " og "me->gr_JSON_ref " . Later
when other methods process the data, we can easily analyze data references an deduce
original data types.
Even though this method is declared as instance/public, you cant call it directly as the class is
defined with private instantiation, meaning that you have to call some kind of factory
method, to get an instance. The class happens to have 3 such methods get_instanse( ),
serialize( ) and deserialize( ). At the same time the class supports the singleton design
patterns, and therefore the client can be sure that the class is instantiated only once.
Later when analyzing the data we need to parse, we always uses the GR_ABAP_DATA
reference to dynamic interpret the data type like scalar fields, internal table or complex data
structure. The GR_JSON_DATA is used as source or target for the JSON data.
Udskrevet: 05-09-2013
Side 23 af 47
<<LISTING 13>>
Listing 13: CONSTRUCTOR( )
*--------------------------------------------------------------------*
* set data references for ABAP and JSON data, both parameters will
*
* always be of some type of data and as we have to support initial
*
* values no checks for value is done here.
*
*--------------------------------------------------------------------*
method constructor.
get reference of ia_ABAP_data into me->gr_ABAP_ref .
get reference of iv_JSON_data into me->gr_JSON_ref .
endmethod.
<</LISTING 13>>
By using data references and field symbols, we are able to work with generic/dynamic data.
+GET_INSTANCE( ): Static/Public.
Public Static Factory method with the purpose to return one instance of the class. The class
supports singleton design patterns and therefore this method check if we already have an
instance, otherwise a new instance is created by command CREATE OBJECT and thereby
the calling the constructor method. Bear in mind that the methods serialize( ) and deserialize(
) also are defined as factory methods and thereby able to instantiate the class. In practical the
get_instance( ) is seldom used. The factory method ensures, that the client do not need to
know if we already have an instance or not, the client just make the call, then it's up to the
class method to take responsibility for only having one instance.
<<LISTING 14>>
Listing 14: GET_INSTANCE ( )
*--------------------------------------------------------------------*
* Get singleton instance, return existing instance or create new one *
* Always reset the ABAP and JSON data references, as the client can *
* make multiple calls using different type of data
*
*--------------------------------------------------------------------*
METHOD get_instance.
IF go_object IS BOUND.
ro_instance = go_object.
go_object->set_ABAP_data( ia_ABAP_data = ia_ABAP_data ).
go_object->set_JSON_data( iv_JSON_data = iv_JSON_data ).
ELSE.
CREATE OBJECT go_object
EXPORTING
ia_ABAP_data = ia_ABAP_data
iv_JSON_data = iv_JSON_data.
Udskrevet: 05-09-2013
Side 24 af 47
ro_instance = go_object.
ENDIF.
+GET_ABAP_DATA( ): instance/public.
Public Instance method, that returns deserialized data as ABAP data type. Every time the
class process ABAP data, this data is stored in the class and thereby easy to return. In
practical this method is seldom used() by the client as the serialize( ) and deserialize( )
methods exports the needed data.
<<LISTING 15>>
Listing 15: GET_ABAP_DATA ( )
*--------------------------------------------------------------------*
* Utility for returning the ABAP data if needed
*
*--------------------------------------------------------------------*
method get_ABAP_data.
field-symbols
<data>
type data .
assign me->gr_ABAP_ref->* to <data> .
ea_data = <data>.
endmethod.
<</LISTING 15>>
+SET_ABAP_DATA( ): instance/public.
Public Instance method used for setting the ABAP data type and then clearing previous
fragments. In practical this method is seldom used by the client as the serialize( ) and
deserialize( ) methods uses the method.
<<LISTING 16 >>
Listing 16: SET_ABAP_DATA ( )
*--------------------------------------------------------------------*
* Set instance variable ABAP_ref and clear any former fragments
*
*--------------------------------------------------------------------*
method set_ABAP_data.
get reference of ia_ABAP_data into zcl_JSON_serializer=>gr_ABAP_ref .
clear zcl_JSON_serializer=>gt_fragments.
endmethod.
Udskrevet: 05-09-2013
Side 25 af 47
<</LISTING 16>>
+GET_JSON_DATA( ): instance/public.
Public Instance method with the purpose to return the serialized JSON string. To be
successful to serialize ABAP data, the data type must be known and must contain data. In
practical this method is seldom used by the client as the serialize( ) and deserialize( ) methods
uses the method.
<<LISTING 17 >>
Listing 17: GET_JSON_DATA ( )
*--------------------------------------------------------------------*
* return the serialized string from fragments to JSON data
*
*--------------------------------------------------------------------*
method get_JSON_data.
concatenate lines of me->gt_fragments into ev_data .
endmethod.
<</LISTING 17>>
+SET_JSON_DATA( ) : instance/public
Public Instance method with the purpose to receive a serialized JSON string containing JSON
formatted data and deserialize to ABAP data of an known data type. In practical this method
is seldom used by the client as the serialize( ) and deserialize( ) methods uses the method.
<<LISTING 18 >>
Listing 18: SET_JSON_DATA ( )
*--------------------------------------------------------------------*
* set class attribute with JSON data and clear former fragments
*
*--------------------------------------------------------------------*
method set_JSON_data.
get reference of iv_JSON_data into zcl_JSON_serializer=>gr_JSON_ref .
clear zcl_JSON_serializer=>gt_fragments.
endmethod.
<</LISTING 18>>
Udskrevet: 05-09-2013
Side 26 af 47
+SERIALIZE( ): static/public
Public Static method with the purpose to serialize any ABAP data source into an JSON string.
This method is one of two often used methods by the client. The method is also a factory
method, creating new instance if one does not already exists.
This method is simple as it only deals with very few task, but it's from here we trigger the
more complex serialization by calling an recursive serializer.
<<LISTING 19>>
Listing 19: SERIALIZE( )
*--------------------------------------------------------------------*
* Static factory method. This method support singletons and therefore*
* the method check if the object is instantiated. If not call the
*
* constructor, else reset the former data and assign the ABAP and
*
* and JSON data.
*
*--------------------------------------------------------------------*
METHOD serialize.
FIELD-SYMBOLS
<data>
TYPE data
.
* Get the instance of the class
go_object = get_instance( ia_ABAP_data = ia_ABAP_data
iv_JSON_data = ev_JSON_data ).
* Assign instance ABAP data reference to field symbols
ASSIGN go_object->gr_ABAP_ref->* TO <data> .
* Do the serialization recursively
go_object->serialize_recursive( EXPORTING
iv_object_name = iv_object_name
ia_data
= <data> ) .
* Get the JSON data
go_object->get_JSON_data( IMPORTING ev_data = ev_JSON_data ).
* Return the Instance
eo_instance = go_object.
ENDMETHOD.
<</LISTING 19>>
Side 27 af 47
-SERIALIZE_RECURSIVE( ): instance/private
Private instance method, with the purpose to serialize the ABAP data type into JSON format.
This method uses recursion for serializing and decompiling deep structures of ABAP data.
The client are not aware about this method. This method is used often and must be optimized.
As the method is private it's secure to change the implementation without disturbing clients.
But it's also in this method we analyse the dynamic ABAP data type by using SAP AG
provided classes and complex ABAP commands. We need to analyse each component of the
dynamic ABAP data type. This method is the hart of the JSON parser, so be careful if you
change this method.
As the JSON data is just a structured string field we can only use the ABAP data for
analyzing.
<<LISTING 20>>
Listing 20: SERIALIZE_RECURSIVE( )
*--------------------------------------------------------------------*
* Method which takes any ABAP data and pass it as a JSON string.
*
* input is any ABAP datatype, and only few of the ABAP datatypes are *
* not supported as more sophisticated datatypes. Basically the data *
* is either a structred datatype like tables and structures or it is *
* a scalar datatype. This method supports data structures and deep
*
* data structures as it supports all typical scalar data types, for *
* seing which scalar types we do not support look in this code.
*
*--------------------------------------------------------------------*
method serialize_recursive.
data:
lv_type
type c ,
lv_comps
type i ,
lv_lines
type i ,
lv_index
type i ,
lv_value
type string .
field-symbols:
<itab>
<comp>
Side 28 af 47
serialize_recursive(
exporting
ia_data
= <comp>
iv_recursive_call = 'X') .
if lv_index < lv_lines .
append c_comma to me->gt_fragments .
endif .
endloop .
append ']' to gt_fragments .
else .
"------------------------------------------------------------------*
" If components are initial and method called from serialize we
*
" are working with a single standalone scarlarfield without name
*
" we only know the data type, not the field name. Therefore the
*
" datatype is used as the fieldname since an JSON object must have *
" an object name and must be surrounded by brackets
*
"{"name":"value"}. Scalar fields are allways single field values, *
" nor part of a structure or tabletype.
*
"If components are initial and method is called recursive from
*
"serial_recursive, the scarlar field is part of an object or array *
" and there by have a name.
"------------------------------------------------------------------*
if lv_comps is initial .
*
field -> scalar
*
todo: format
lv_value = ia_data .
replace all occurrences of '\'
in lv_value with '\\' .
replace all occurrences of ''''
in lv_value with '\''' .
replace all occurrences of '"'
in lv_value with '\"' .
replace all occurrences of '&'
in lv_value with '\&' .
replace all occurrences of cl_ABAP_char_utilities=>cr_lf
in lv_value with '\r\n' .
replace all occurrences of cl_ABAP_char_utilities=>newline
in lv_value with '\n' .
replace all occurrences of cl_ABAP_char_utilities=>horizontal_tab
in lv_value with '\t' .
replace all occurrences of cl_ABAP_char_utilities=>backspace
in lv_value with '\b' .
replace all occurrences of cl_ABAP_char_utilities=>form_feed
in lv_value with '\f' .
if iv_recursive_call is initial.
if iv_object_name is not initial.
concatenate '{"' iv_object_name '":' '"' lv_value '"' '}'
into lv_value .
else.
case lv_type.
when cl_ABAP_typedescr=>typekind_num.
concatenate '{"num":' '"' lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_date.
concatenate '{"date":' '"' lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_packed.
concatenate '{"packed":' '"' lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_time.
concatenate '{"time":' '"' lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_char.
concatenate '{"char":' '"' lv_value '"' '}' into lv_value .
Udskrevet: 05-09-2013
Side 29 af 47
when cl_ABAP_typedescr=>typekind_hex.
concatenate '{"hex":' '"' lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_float.
concatenate '{"float":' '"' lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_int.
concatenate '{"int":' '"' lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_int1.
concatenate '{"int1":' '"' lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_int2.
concatenate '{"int2":' '"' lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_w.
concatenate '{"Wide":' '"' lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_oref.
concatenate '{"Object reference, not supported":'(j01) '"'
lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_string.
concatenate '{"string":' '"' lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_xstring.
concatenate '{"xtring":' '"' lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_dref.
concatenate '{"Data reference, not supported":'(j02) '"'
lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_class.
concatenate '{"Class reference, not supported":'(j03) '"'
lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_intf.
concatenate '{"Class reference, not supported":'(j04) '"'
lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_any.
concatenate '{"Type Any, not supported":'(j05) '"'
lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_data.
concatenate '{"Type data, not supported":'(j06) '"'
lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_simple.
concatenate '{"Type clike, not supported":'(j07) '"'
lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_csequence.
concatenate '{"Type csequence, not supported":'(j08) '"'
lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_xsequence.
concatenate '{"Type xsequence, not supported":'(j09) '"'
lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_numeric.
concatenate '{"numeric":' '"' lv_value '"' '}' into lv_value .
when cl_ABAP_typedescr=>typekind_iref.
concatenate '{"Instance reference, not supported":'(j10) '"'
lv_value '"' '}' into lv_value .
when others.
concatenate '{"NOT SUPPORTED":'(j11) '"' lv_value '"' '}'
into lv_value .
endcase.
endif.
else.
concatenate '"' lv_value '"' into lv_value .
endif.
condense lv_value.
append lv_value to me->gt_fragments .
else .
"------------------------------------------------------------------*
" Structure
Udskrevet: 05-09-2013
Side 30 af 47
"------------------------------------------------------------------*
data lv_typedescr type ref to cl_ABAP_structdescr .
field-symbols <ABAPcomp> type ABAP_compdescr .
append '{' to me->gt_fragments .
lv_typedescr ?= cl_ABAP_typedescr=>describe_by_data( ia_data ) .
loop at lv_typedescr->components assigning <ABAPcomp> .
lv_index = sy-tabix .
concatenate '"' <ABAPcomp>-name '"' c_colon into lv_value .
translate lv_value to lower case .
append lv_value to me->gt_fragments .
assign component <ABAPcomp>-name of structure ia_data to <comp> .
serialize_recursive(
exporting
ia_data
= <comp>
iv_recursive_call = 'X' ).
if lv_index < lv_comps .
append c_comma to me->gt_fragments .
endif .
endloop .
append '}' to me->gt_fragments .
endif .
endif .
endmethod.
<</LISTING 20>>
This method analyses the ABAP data type/structure by using some of the more complex
ABAP commands and build-in classes, it's worth the effort to fully understand what's
happening. use this solution as inspiration when working with dynamic/generic data.
+DESERIALIZE( ): static/public
Public static method, in order to deserialize JSON string into any ABAP data source.
This method are one of two often used methods for the ABAP developer. The method is also
a factory method, creating new instance if one does not already exists. Serialize is the most
common used method.
This method is simple as it only deals with very few task, but it's from here we trigger the
more complex deserialization by calling an recursive deserializer.
<<LISTING 21 >>
Listing 21: DESERIALIZE ( )
*--------------------------------------------------------------------*
* Static factory method. This method support singletons and therefore*
Udskrevet: 05-09-2013
Side 31 af 47
TYPE data .
Side 32 af 47
IF sy-subrc NE 0.
RAISE format_error.
ENDIF.
* set the ABAP data in instance
go_object->set_ABAP_data( EXPORTING
ia_ABAP_data = ea_ABAP_data ).
<</LISTING 21>>
Private instance method with the purpose to deserialize JSON into ABAP data source, this
method uses recursion for decompiling deep structures. The client is not aware about this
method.
This method is used often and must be optimized. As the method is private it's safe to change
the implementation without disturbing clients.
But it's also in this method we analyse the dynamic ABAP data type by using SAP AG
provided classes and complex ABAP commands. We need to analyse each component of the
dynamic ABAP data type. This method is the hart of the JSON parser, so be careful if you
change this method.
As the JSON data is just a structured string field we can only use the ABAP data for
analyzing, therefore the starting point is the ABAP data. The method then looks up in the
fragments table to find something that we expect from the ABAP data point of view, if we do
not find what we expected you get the format error exception.
Udskrevet: 05-09-2013
Side 33 af 47
<<LISTING 22 >>
Listing 22: DESERIALIZE_RECURSIVE ( )
*--------------------------------------------------------------------*
* As with serialize this method serialize fragments but instead of
*
* adding fragment this method delete fragment from fragments and
*
* update the ABAP data
*
*--------------------------------------------------------------------*
method deserialize_recursive.
data:
lv_type
type c
"#EC NEEDED
,lv_comps
type i
"#EC NEEDED
,lv_lines
type i
,lv_index
type i
,lo_typedescr
type ref to cl_ABAP_structdescr
,lo_datadescr
type ref to cl_ABAP_datadescr
,lo_tabledescr
type ref to cl_ABAP_tabledescr
,lo_elemdescr
type ref to cl_ABAP_elemdescr
"#EC NEEDED
,lv_name
type string
,lv_data
type string
,lr_row
type ref to data
.
field-symbols:
<ABAPcomp>
<atab>
<itab>
<xtab>
<htab>
<stab>
<comp>
type
type
type
type
type
type
type
ABAP_compdescr,
any table,
standard table ,
index table,
hashed table,
sorted table,
any .
case lo_tabledescr->table_kind.
when cl_ABAP_tabledescr=>tablekind_any.
"ANY TABLE includes all table types and therefore we might
"not reach this code
assign ca_data to <atab> .
read table gt_fragments index 1 into lv_data.
concatenate '"' iv_name '": ' into lv_name.
translate: lv_name to lower case
,lv_data to lower case.
condense lv_name.
single row for table without array tag?
if lv_data = '{'.
create data lr_row like line of <itab>.
loop at gt_fragments into lv_data.
assign lr_row->* to <comp>.
Udskrevet: 05-09-2013
Side 34 af 47
deserialize_recursive(
changing
ca_data = <comp>
exceptions
format_error = 1 ) .
if sy-subrc ne 0.
raise format_error.
endif.
append <comp> to <itab>.
endloop.
array of 1:m rows with table tag
elseif lv_data = '[' or lv_data = lv_name.
if lv_data ne '['.
delete gt_fragments index 1.
endif.
delete gt_fragments index 1.
read table gt_fragments index 1 into lv_data.
if lv_data eq ']'.
delete gt_fragments index 1.
return. "Empty table, which is not an error
endif.
create data lr_row like line of <atab>.
loop at gt_fragments into lv_data.
assign lr_row->* to <comp>.
deserialize_recursive(
changing
ca_data = <comp>
exceptions
format_error = 1 ) .
if sy-subrc ne 0.
raise format_error.
endif.
collect <comp> into <atab>.
read table gt_fragments index 1 into lv_data.
if lv_data = ','. "new row is assumed
delete gt_fragments index 1.
continue.
elseif lv_data = ']'.
delete gt_fragments index 1.
exit.
else.
raise format_error.
endif.
endloop.
else.
raise format_error.
endif.
when cl_ABAP_tabledescr=>tablekind_std.
assign ca_data to <itab> .
read table gt_fragments index 1 into lv_data.
concatenate '"' iv_name '": ' into lv_name.
translate: lv_name to lower case
,lv_data to lower case.
condense lv_name.
Udskrevet: 05-09-2013
Side 35 af 47
Udskrevet: 05-09-2013
Side 36 af 47
Udskrevet: 05-09-2013
Side 37 af 47
Udskrevet: 05-09-2013
Side 38 af 47
Udskrevet: 05-09-2013
Side 39 af 47
endcase.
"----------------------------------------------------------------*
" Type Element
"----------------------------------------------------------------*
when cl_ABAP_datadescr=>kind_elem.
lo_elemdescr ?= cl_ABAP_elemdescr=>describe_by_data( ca_data ) .
"Field part of a data structure
if iv_name is not initial.
concatenate '"' iv_name '":' into lv_name.
translate lv_name to lower case.
read table gt_fragments index 1 into lv_data.
translate lv_data to upper case.
translate lv_name to upper case.
delete gt_fragments index 1.
read table gt_fragments index 1 into lv_data.
replace all occurrences of '"' in lv_data with ''.
condense lv_data.
case lv_data.
when 'true'.
ca_data = '1'.
when 'false'.
ca_data = '0'.
when 'null'.
ca_data = ''.
when others.
ca_data = lv_data.
endcase.
delete gt_fragments index 1.
else. "Scalar field (single field)
read table gt_fragments index 1 into lv_data.
if lv_data = '{'.
delete gt_fragments index 1.
read table gt_fragments index 1 into lv_data.
find first occurrence of ':' in lv_data.
if sy-subrc ne 0.
raise format_error.
endif.
delete gt_fragments index 1.
read table gt_fragments index 1 into lv_data.
if lv_data(1) = '"'.
replace all occurrences of '"' in lv_data with ''.
condense lv_data.
ca_data = lv_data.
else.
replace all occurrences of '"' in lv_data with ''.
condense lv_data.
case lv_data.
when 'true'.
ca_data = '1'.
when 'false'.
ca_data = '0'.
when 'null'.
ca_data = ''.
when others.
ca_data = lv_data.
endcase.
endif.
delete gt_fragments index 1.
read table gt_fragments index 1 into lv_data.
Udskrevet: 05-09-2013
Side 40 af 47
if lv_data = '}'.
delete gt_fragments index 1.
exit.
else.
raise format_error.
endif.
elseif lv_data(1) = '"'.
read table gt_fragments index 1 into lv_data.
replace all occurrences of '"' in lv_data with ''.
condense lv_data.
ca_data = lv_data.
delete gt_fragments index 1.
exit.
else.
read table gt_fragments index 1 into lv_data.
if lv_data(1) = '"'.
replace all occurrences of '"' in lv_data with ''.
condense lv_data.
ca_data = lv_data.
else.
replace all occurrences of '"' in lv_data with ''.
condense lv_data.
case lv_data.
when 'true'.
ca_data = '1'.
when 'false'.
ca_data = '0'.
when 'null'.
ca_data = ''.
when others.
ca_data = lv_data.
endcase.
endif.
delete gt_fragments index 1.
exit.
endif.
endif.
when cl_ABAP_datadescr=>kind_struct.
lo_typedescr ?= cl_ABAP_structdescr=>describe_by_data( ca_data ) .
lv_lines = lines( lo_typedescr->components ) .
read table gt_fragments index 1 into lv_data.
if lv_data = '{'.
delete gt_fragments index 1.
else.
read table gt_fragments index 2 into lv_data.
if lv_data = '{'.
delete gt_fragments index 1.
delete gt_fragments index 1.
else.
raise format_error.
endif.
endif.
loop at lo_typedescr->components assigning <ABAPcomp> .
lv_index = sy-tabix.
assign component <ABAPcomp>-name of structure ca_data to <comp> .
deserialize_recursive(
exporting
iv_name = <ABAPcomp>-name
changing
Udskrevet: 05-09-2013
Side 41 af 47
ca_data = <comp>
exceptions
format_error = 1 ) .
if sy-subrc ne 0.
raise format_error.
endif.
if lv_index < lv_lines.
read table gt_fragments index 1 into lv_data.
if lv_data = ','.
delete gt_fragments index 1.
elseif lv_data = '}'.
exit. "structure closed before last field, this is acceptable
else.
raise format_error.
endif.
endif.
endloop .
read table gt_fragments index 1 into lv_data.
if lv_data = '}'.
delete gt_fragments index 1.
else.
raise format_error.
endif.
when cl_ABAP_datadescr=>kind_ref.
raise datatype_not_supported.
when cl_ABAP_datadescr=>kind_class.
raise datatype_not_supported.
when cl_ABAP_datadescr=>kind_intf.
raise datatype_not_supported.
when others.
raise datatype_not_supported.
endcase.
endmethod.
<</LISTING 22>>
This method analyses the ABAP data type/structure by using some of the more complex
ABAP commands and build-in classes, it's worth the effort to fully understand what's
happening. use this solution as inspiration when working with dynamic/generic data.
+COMPILE_JSON( ): static/public
Public static method used for combining multiple JSON Objects into a combined JSON
object. This method is more or less a helper method, as the client often works with several
JSON objects and often needs to merge JSON objects into one merged object.
Udskrevet: 05-09-2013
Side 42 af 47
<<LISTING 23 >>
Listing 23: COMPILE_JSON ( )
*--------------------------------------------------------------------*
* This method combines a collection of JSON Objects into one single *
* JSON object by surrounding the members with "{" and "}".
*
*--------------------------------------------------------------------*
method compile_JSONs.
constants:
co_true
type boolean_01 value 1
,co_false
type boolean_01 value 0 .
data:
lv_with_root
field-symbols:
<JSON_object>
type zJSON_object.
clear ev_JSON_data.
loop at it_JSON_objects assigning <JSON_object>.
if <JSON_object>-id is initial.
lv_with_root = co_false.
concatenate ev_JSON_data '[' <JSON_object>-tx into ev_JSON_data.
else.
lv_with_root = co_true.
concatenate ev_JSON_data ',"' <JSON_object>-id '":' <JSON_object>-tx
into ev_JSON_data.
endif.
endloop.
shift ev_JSON_data.
if lv_with_root = co_true.
concatenate '{' ev_JSON_data '}' into ev_JSON_data.
endif.
endmethod.
<</LISTING 23>>
-FRAGMENTS_JSON_DATA( ): instance/private
This method is a private instance helper function that facilitates the class itself with splitting
JSON data into fragments within an ordinary internal table. The method is called by the
deserialize method. This method have knowledge about the JSON notation. The method takes
the JSON data as input and split it up into the fragments that are in interest.
<<LISTING 24 >>
Udskrevet: 05-09-2013
Side 43 af 47
TYPE data .
REFRESH gt_fragments.
lv_JSON_string = iv_JSON_data.
*** do not condense JSON_string with no gaps as spaces between words do disapear
*** CONDENSE JSON_string no-gaps.
lv_length = STRLEN( lv_JSON_string ).
"validate JSON_string, the first character must be { or [
"the last be be } or ]
IF lv_JSON_string+lv_i(1) NE '[' AND
lv_JSON_string+lv_i(1) NE '{'.
RAISE format_error.
ENDIF.
SUBTRACT 1 FROM lv_length.
IF lv_JSON_string+lv_length(1) NE ']' AND
lv_JSON_string+lv_length(1) NE '}'.
RAISE format_error.
ENDIF.
ADD 1 TO lv_length.
WHILE lv_i < lv_length.
lv_c = lv_JSON_string+lv_i(1).
CASE lv_c.
when ' '.
IF lv_quotation_block_on = true.
CONCATENATE lv_fragment lv_c INTO lv_fragment RESPECTING BLANKS.
endif.
WHEN '{' OR '}' OR '[' OR ']' .
IF lv_quotation_block_on = true.
CONCATENATE lv_fragment lv_c INTO lv_fragment.
ELSE.
IF lv_fragment IS NOT INITIAL.
APPEND lv_fragment
TO gt_fragments.
ENDIF.
APPEND lv_JSON_string+lv_i(1) TO gt_fragments.
CLEAR lv_fragment.
ENDIF.
WHEN ','.
IF lv_quotation_block_on = true.
Udskrevet: 05-09-2013
Side 44 af 47
<</LISTING 24>>
The fragments method is very important and processed the JSON data byte by byte. Be
careful if you make changes in this method and always check impact of changes on the
performance. Be aware about the lv_qoutation_block_on which track if a string has been
started or finished.
Lets see what's behind the scene in the debugger when we want to deserialize the JSON
<<LISTING 25 >>
Listing 25: JSON Result
{
"MyData": {
"mandt": "000",
"carrid": "AA",
"connid": "0017",
"fldate": "20110427",
"price": "422.94 ",
"currency": "USD",
"planetype": "747-400",
"seatsmax": "385 ",
"seatsocc": "365 ",
"paymentsum": "188462.26 ",
"seatsmax_b": "31 ",
"seatsocc_b": "30 ",
"seatsmax_f": "21 ",
"seatsocc_f": "18 "
}
Udskrevet: 05-09-2013
Side 45 af 47
<</LISTING 25>>
Here you see that the fragments table contains the field values and JSON notation characters.
We use the JSON control characters to understand what we received and the field values for
mapping into the ABAP data structure. But keep in mind that this method mission is only
about splitting the JSON data into JSON control characters and field values. In this example
all fields are string fields.
Udskrevet: 05-09-2013
Side 46 af 47
Summary
Having build your own JSON parser for ABAP, you have a common tool for all your
comming projects that need to parse to/from JSON. And even if you not need a JSON
parser, you now got some ideas to build other parsers like an XML parser. Do also look
into SCN to find other similar solutions.
On the Web
http://www.bgs.dk
informations
http://JSON.org/
http://JSONlint.com/
http://scn.sap.com
Udskrevet: 05-09-2013
Side 47 af 47