Json Article

47
BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark Udskrevet: 05-09-2013 Side 1 af 47 JSON parser for ABAP Context JSON parser for ABAP developed on SAP Netweaver release 7.0. What is a JSON Parser for ABAP. 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 JSON parser for ABAP is a class in ABAP that is able to interpret JSON and return ABAP Data and vice versa. With JSON parser for ABAP you then can support RESTful web services as integration with SAP. Why is JSON interesting for ABAP developers. JSON is interesting for ABAP developers, because JSON with BSP (Business Server Pages) allow simple exposure of data from SAP to the Internet, without the use of SOA and the administration that follows. From ABAP you could also call web services on the internet, that return JSON as result. Nearly every web developer knows the JSON format, web tools and programming languages support JSON directly in the runtime environment. By using BSP and ICF services, we are able to provide URI/URL for the web developer, that can be assigned to classes or functions within SAP. Hereby we are able to deliver any data from SAP or receive any data to SAP, that any ABAP developer can work with, using well known tools like RFC function modules or ABAP classes. If we build a REST service, within ABAP that hide special HTTP issues from the ABAP developer, and hide special SAP knowledge from the Web developer, we will be able to expose any SAP data on the internet or on any mobile devices, that can be used by any kind of web solution, whether nor using SAP web tools like Web Dynpro, Portal, ITS and similar. The only piece missing, is some kind of utility that can serialize ABAP data to JSON or deserialize JSON data to ABAP data. This missing link, is what this article is about. The REST service will be explained and described in a later article. On SDN http://www.sdn.sap.com/ you can read other articles and source code, that address the same issue. I have been using several of these solutions, but found they were too performance demanding, or was missing the deserialize function. Therefore I spend some time to figure out another way of serializing and deserializing. What is the benefit for you... Ó The article explain some of the choises in the solution, Ó The article show you the code behind the JSON parser within ABAP,

description

json abap

Transcript of Json Article

Page 1: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 1 af 47

JSON parser for ABAP

Context JSON parser for ABAP developed on SAP Netweaver release 7.0.

What is a JSON Parser for ABAP. 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 JSON parser for ABAP is a class in ABAP that is able to interpret JSON and return ABAP Data and vice versa. With JSON parser for ABAP you then can support RESTful web services as integration with SAP.

Why is JSON interesting for ABAP developers. JSON is interesting for ABAP developers, because JSON with BSP (Business Server Pages) allow simple exposure of data from SAP to the Internet, without the use of SOA and the administration that follows. From ABAP you could also call web services on the internet, that return JSON as result. Nearly every web developer knows the JSON format, web tools and programming languages support JSON directly in the runtime environment. By using BSP and ICF services, we are able to provide URI/URL for the web developer, that can be assigned to classes or functions within SAP. Hereby we are able to deliver any data from SAP or receive any data to SAP, that any ABAP developer can work with, using well known tools like RFC function modules or ABAP classes. If we build a REST service, within ABAP that hide special HTTP issues from the ABAP developer, and hide special SAP knowledge from the Web developer, we will be able to expose any SAP data on the internet or on any mobile devices, that can be used by any kind of web solution, whether nor using SAP web tools like Web Dynpro, Portal, ITS and similar. The only piece missing, is some kind of utility that can serialize ABAP data to JSON or deserialize JSON data to ABAP data. This missing link, is what this article is about. The REST service will be explained and described in a later article. On SDN http://www.sdn.sap.com/ you can read other articles and source code, that address the same issue. I have been using several of these solutions, but found they were too performance demanding, or was missing the deserialize function. Therefore I spend some time to figure out another way of serializing and deserializing.

What is the benefit for you... The article explain some of the choises in the solution, The article show you the code behind the JSON parser within ABAP,

Page 2: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 2 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.

What you should know... You need to be an experienced ABAP developer, You need some skils and knowledge about ABAP objects, You should have an idea of what object design pattern is, You should be familar with recursive calls, You should have some understanding for web developments, You must read about JSON at http:///www.json.org.

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.

Page 3: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 3 af 47

Figur 1 JSON Validator 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.

JSON Notation implementation in ABAP The JSON notation is simple as it only contains 5 elements object, array, value, string and number. Here we look into the 5 elements implementation. Before looking into the final implementation within the ABAP class, let's have a closer look at the JSON notation Syntax by looking into these 5 elements. Here we look into the issues where JSON and ABAP have different notations. As we in ABAP must use ABAP known data type, the JSON Parser for ABAP, build upon the assumption, that we in ABAP is familiar with the data type, structure, table etc, that we are able to send or receive as JSON. In the following we walk trough the JSON notation looking for issues in regards to the implementation in ABAP.

Page 4: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 4 af 47

Object

Figur 2 JSON 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 s_mandt ,carrid TYPE s_carr_id ,connid TYPE s_conn_id ,fldate TYPE s_date ,price TYPE s_price ,currency TYPE s_currcode ,planetype TYPE s_planetye ,seatsmax TYPE s_seatsmax ,seatsocc TYPE s_seatsocc ,paymentsum TYPE s_sum

Page 5: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 5 af 47

,seatsmax_b TYPE s_smax_b ,seatsocc_b TYPE s_socc_b ,seatsmax_f TYPE s_smax_f ,seatsocc_f TYPE s_socc_f ,END OF ty_sflight. DATA: JSON_data TYPE string ,ABAP_data TYPE ty_sflight. * select some data from sflight into a data structure. SELECT SINGLE * FROM sflight INTO ABAP_data WHERE carrid = 'AA' AND connid = '17' AND fldate = '20110427' . * errorhandling is eliminated for simplification. if sy-subrc ne 0. stop. endif. * having the ABAP data we like to serialize these data to JSON zcl_JSON_serializer=>serialize( EXPORTING IV_OBJECT_NAME = 'MyData' ia_ABAP_data = ABAP_data IMPORTING ev_JSON_data = JSON_data ). WRITE: / JSON_data. * clear the ABAP data CLEAR ABAP_data. * having the JSON data we like to deserialize these data to ABAP zcl_JSON_serializer=>deserialize( EXPORTING IV_OBJECT_NAME = 'MyData' iv_JSON_data = JSON_data IMPORTING ea_ABAP_data = ABAP_data exceptions format_error = 1 ). if sy-subrc ne 0. write: / 'Format error in the JSON data'. endif. WRITE: / ABAP_data-mandt ,ABAP_data-carrid ,ABAP_data-connid ,ABAP_data-fldate ,ABAP_data-price ,ABAP_data-currency ,ABAP_data-planetype ,ABAP_data-seatsmax ,ABAP_data-seatsocc ,ABAP_data-paymentsum ,ABAP_data-seatsmax_b ,ABAP_data-seatsocc_b ,ABAP_data-seatsmax_f ,ABAP_data-seatsocc_f. <</LISTING 1>>

Page 6: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 6 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.

Figur 3 ABAP data 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

Page 7: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 7 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 TYPE s_sum ,JSON_data TYPE string. * select som data from sflight into a scalar field. SELECT SINGLE paymentsum FROM sflight INTO ABAP_data WHERE carrid = 'AA' AND connid = '17' AND fldate = '20110427' . * having the ABAP data we like to serialize this data to JSON zcl_JSON_serializer=>serialize( EXPORTING iv_object_name = 'Expences' ia_ABAP_data = ABAP_data IMPORTING ev_JSON_data = JSON_data ). * now look into the result. WRITE: / JSON_data.

Page 8: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 8 af 47

* clear the structure CLEAR ABAP_data. * having the JSON data we like to deserialize this data to ABAP zcl_JSON_serializer=>deserialize( EXPORTING iv_object_name = 'Expences' iv_JSON_data = JSON_data IMPORTING ea_ABAP_data = ABAP_data ). WRITE: / ABAP_data.

<</LISTING 4>> <<LISTING 5 >> Listing 4: Scalar field as JSON { "Expences": "188462.26 " }

<</LISTING 5>> JSON ABAP { "field1": "First Value", "field2": "second Value", "field3": "third value" }

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.

{ "MANDT": "000", "NAME": "Graham Nicholson", "AGE": "34" }

Fixed nameless data Structure

{ "Data": { "MANDT": "000", "NAME": "Graham Nicholson", "AGE": "34"} }

Fixed named data Structure

{ "noname":true }

Must result in the value 1 (one) as this value simulates true.

{ "noname":false }

Must result in the value 0 (one) as this value simulates false.

{"anyname":"611"} Even though the fieldname is not the same as the fieldname in ABAP, this value is accepted and filled into the field you assigned in ABAP. This only works for scalar fields

Page 9: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 9 af 47

Array

Figur 4 JSON 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"] In ABAP this array is implmented as a standard table or

tabletype of given datatype. I ABAP implementeres dette array med værdier som et tabletype or data element.You need to make an agreement with your partner of for the datatype.

[ {"Name":"Patrick","Birth":"1934","Month":"11"}, {"Name":"Carl","Birth":"1964","Month":"9"}, {"Name":"Jonas","Birth":"1988","Month":"3"}, {"Name":"Mogens","Birth":"1955","Month":"10"} ]

In ABAP this array is implmented as a standard table of a given data stucture that has name, birth and month as fields.

["611"] In JSON this is an array with ony one row, in ABAP it's defined as a standard table of given data type and not as a structure.

[ { "F": 23 } ] In JSON this is also an array, but now the field has a name. In ABAP this is defined as a table of a given data structure which only contains one field.

Lets look at an example of ordinary internal table of a structure:

Page 10: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 10 af 47

Figur 5 DDIC structure zmod_condition <<LISTING 6 >> Listing 6: Code example for table of simple data structure DATA: li_conditions TYPE TABLE OF zmod_condition ,lst_condition TYPE zmod_condition ,JSON_data TYPE string . * get some data SELECT * INTO TABLE li_conditions FROM zmod_condition. "create some JSON CLEAR JSON_data. zcl_JSON_serializer=>serialize( EXPORTING ia_ABAP_data = li_conditions IMPORTING ev_JSON_data = JSON_data ). WRITE:/ 'Serialize:' ,50 JSON_data. REFRESH li_conditions. "create some ABAP data zcl_JSON_serializer=>deserialize( EXPORTING iv_JSON_data = JSON_data IMPORTING ea_ABAP_data = li_conditions ). WRITE:/ 'Deserialize:' . LOOP AT li_conditions INTO lst_condition. WRITE:/50 sy-tabix, lst_condition-mandt, space, lst_condition-procid, space, lst_condition-condid, space, lst_condition-bis, space, lst_condition-cond_class, space, lst_condition-cond_method, space, lst_condition-dummy_condition, space. ENDLOOP. ULINE.

<</LISTING 6>>

Page 11: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 11 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.

Figur 6 ABAP result as ITAB And the let's look at a internal table of scalar fields: <<LISTING 8 >> Listing 8: Code example for table of single data type (array) "zprocid is of data type CHAR 10 DATA: li_array TYPE TABLE OF zprocid ,lv_element TYPE zprocid ,JSON_data TYPE string . SELECT procid INTO TABLE li_array FROM zmod_process.

Page 12: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 12 af 47

"create some JSON CLEAR JSON_data. zcl_JSON_serializer=>serialize( EXPORTING ia_ABAP_data = li_array IMPORTING ev_JSON_data = JSON_data ). WRITE:/ 'Serialize:' ,50 JSON_data. REFRESH li_array. "create some ABAP data zcl_JSON_serializer=>deserialize( EXPORTING iv_JSON_data = JSON_data IMPORTING ea_ABAP_data = li_array ). WRITE:/ 'Deserialize:' . LOOP AT li_array INTO lv_element. WRITE:/50 sy-tabix, lv_element. ENDLOOP. ULINE.

<</LISTING 8>> <<LISTING 9 >> Listing 9: Result of Array example [ "BPEL", "D01", "D02", "L01", ... "X01", "X01" ] <</LISTING 9>>

Page 13: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 13 af 47

Figur 7 ABAP debug table

Value

Figur 8 JSON Value A value can be a string in double quotes, a number, true, false, null, an object or an

Page 14: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 14 af 47

array. The object and array can be nested. A value corresponds to the row element of a table type of data element within ABAP. The ABAP parser must be able to support all nearly types of values, even though that ABAP does not directly support true, false and null. In ABAP you can't have a table with elements of type any, you need to know the exact data type of a table, your ABAP table supports all build-in data types and structures equivalent to JSONs object. But in ABAP we can't have tables of mixed data types, unless you know the sequence of the mixed data types and that the sequence is fixed. If you make an agreement with your partner, that you support mixed data types in specific fixed sequence, then we could build the table as type data reference. But that will mean that you have a fixed number of rows which does not make sense. Instead the JSON parser for ABAP support that you interpret the mixed data types as strings. However the JSON parser for ABAP will not support values with mixed data types including JSON objects or JSON arrays, se example. Remember that values are used in arrays and not in objects. Examples: [ "Anders Mattesen" ]

Simple string value, in ABAP we have type data type char with a fixed length and string with variable length. You are better of using the string type as many http services uses this type. Dates are often representen as string values on the internet. I fyou have a date, you must make an agreement on the formatting.

[ 365 ]

Simple number, in ABAP we works with different types of build-in numbers like byte, numeric, decimal, integers and packed. It's most important that both client and server make agreement on the type, length, decimal points and format.

[ true ]

The value true known as boolean value is not known in ABAP, but you could represent it as integer value 1, or you can use SAP's funny boolean value 'X'. In this JSON parser I use the value 1 for true. true must be in lowercase

[ false ]

The value false known as boolean value is not known in ABAP, but you could represent it as integer value 0, or you can use SAP's funny boolean value ' '. In this JSON parser I use the value 0 for false. false must be in lowercase

[ null ]

The value null known as neither blank, zero or initial value but simple no value, is not known in ABAP. In the ABAP parser I interprete null as the clear version of the field and therefore the field is cleared using clear command. null must be in lowercase

[ null, "first string,

Here we have array of mixed values. If we know the sequence and the data type of each row, we can support this array in ABAP. But this means that the

Page 15: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 15 af 47

1234, true, false, "second string" ]

array always must have a fixed number of rows. In ABAP you are better of by interpreting array of mixed data types as table of strings. This is the reason that the JSON parser for ABAP supports array of strings and not table of data references.

[ null, "first string", 1234, true, { "Name": "JakeSmith", "Age": "55", "Sex": "Male" }, false, "secondstring" ]

Even though this is a valid JSON combination we can't support this in ABAP as the table contains mixed data types as values and so include the JSON object here as a datastructure. We would have the same issue with values including arrays.

<<LISTING 10 >> Listing 10: Code example for mixed data types as values DATA: JSON_data TYPE string ,ABAP_data TYPE string ,ABAP_table TYPE STANDARD TABLE OF string. JSON_data = '[ null,"first string",1234, true, false,"second string"]'. WRITE: / JSON_data. * clear the ABAP data CLEAR ABAP_data. REFRESH ABAP_table. * having the JSON data we like to deserialize these data to ABAP zcl_JSON_serializer=>deserialize( EXPORTING * iv_object_name = '' iv_JSON_data = JSON_data IMPORTING ea_ABAP_data = ABAP_table EXCEPTIONS format_error = 1 ). IF sy-subrc NE 0. WRITE: / 'Format error in the JSON data'. ENDIF. LOOP AT ABAP_table INTO ABAP_data. WRITE:/ ABAP_data. ENDLOOP.

<</LISTING 10>> <<LISTING 11 >> Listing 11: Code example for mixed data types as integer DATA: JSON_data TYPE string ,ABAP_data TYPE i ,ABAP_table TYPE STANDARD TABLE OF i. FIELD-SYMBOLS:

Page 16: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 16 af 47

<ABAP> TYPE ANY. * initialize APPEND 233 TO ABAP_table. APPEND 234 TO ABAP_table. APPEND 235 TO ABAP_table. * having the ABAP data we like to serialize these data to JSON zcl_JSON_serializer=>serialize( EXPORTING * iv_object_name = '' ia_ABAP_data = ABAP_table IMPORTING ev_JSON_data = JSON_data ). WRITE: / JSON_data. * clear the ABAP data CLEAR ABAP_data. REFRESH ABAP_table. * having the JSON data we like to deserialize these data to ABAP zcl_JSON_serializer=>deserialize( EXPORTING * iv_object_name = '' iv_JSON_data = JSON_data IMPORTING ea_ABAP_data = ABAP_table EXCEPTIONS format_error = 1 ). IF sy-subrc NE 0. WRITE: / 'Format error in the JSON data'. ENDIF. LOOP AT ABAP_table INTO ABAP_data. WRITE:/ ABAP_data. ENDLOOP.

<</LISTING 11>>

Page 17: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 17 af 47

String

Figur 9 JSON 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.

Page 18: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 18 af 47

Examples: { "simple_string": "Anders Mattesen" } Simple string value wrapped into quotes. {"date": "20131215"} The ABAP JSON parser will interpret this date as a

string value, so any agreement af the format must be solved after returning data from/to the parser.

{"control": "\""} Ignored likewise the values \", \\, \/, \b, \f, \n, \r, \t, or \u four-hex-digits will be ignored. JSON Lint Validator does not accept control either.

Number

Figur 10 JSON 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"} 0002277891, here we use data type N in ABAP. {"packed":"11223377 "} 11223377, here we use data type P in ABAP. {"noname": 23.45 } 23.45, here we use data type Decimals in ABAP {"noname": -76.42 } 76.42-, here we use data type Decimals in ABAP {"noname": 1.0055000075E+8 } 1.0055000075000000E+08, here we use data type

FLOAT For Code examples see scalar fields in the test program.

Page 19: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 19 af 47

ABAP Test program It's good practice to build a test program whenever you need to build compilers/intepreters as you need lots of testcases and many more than I can show in this article.

Figur 11 ABAP Test program

ABAP solution implemented as a class After looking at the issues in regards to implement JSON notation as parser in ABAP, we look deeply into the implemented ABAP code. The objective for developing a JSON parser as a class was to build a fast and simple to use class, that any ABAP developer can use, without knowing how it works. The class supports the JSON format, as describe at http://www.JSON.org with only very few limitations. The class is named ZCL_JSON_SERIALIZER and is a concrete class. The JSON created by the class has been tested using http://JSONlint.com.

Concept

Using a class for the encapsulation gives a number of advantages, like hiding the complexity, reuse ability, using the class as placeholder for data. Ability to optimize or maintain code centrally without disturbing clients as long as the methods interface does not change. I wanted the class to support singleton design patterns, to ensure that we only have one single instance of the object. On the other hand it was essential that the class was easy to use for clients without knowledge about object oriented ABAP.

Page 20: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 20 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:

GET REFERENCE OF, FIELD-SYMBOLS, ASSIGN, DESCRIBE FIELD, CL_ABAP_TYPEDESCR, CL_ABAP_DATADESCR, CL_ABAP_COMPLEXDESCR, CL_ABAP_STRUCTDESCR, CL_ABAP_TABLEDESCR, CL_ABAP_ELEMDESCR, CL_ABAP_REFDESCR, CL_ABAP_OBJECTDESCR, CL_ABAP_CLASSDESCR, CL_ABAP_INTFDESCR.

Page 21: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 21 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

Page 22: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 22 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

Page 23: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 23 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>> This method reuses SAP standard classes.

+CONSTRUCTOR(DATA) : Instance/public.

Figur 15 Constructor signature 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.

Page 24: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 24 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.

Figur 16 Get Instance signature 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.

Page 25: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 25 af 47

ro_instance = go_object. ENDIF.

ENDMETHOD. <</LISTING 14>>

+GET_ABAP_DATA( ): instance/public.

Figur 17 Get_ABAP_Data signature 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.

Figur 18 SET_ABAP_DATA signature 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.

Page 26: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 26 af 47

<</LISTING 16>>

+GET_JSON_DATA( ): instance/public.

Figur 19 GET_JSON_DATA signatures 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

Figur 20 SET_JSON_DATA Signatures 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>>

Page 27: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 27 af 47

+SERIALIZE( ): static/public

Figur 21 SERIALIZE Signatures 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>> Serialize supports singleton patterns by calling the get_instance( ) method. After assigning the ABAP data reference this method calls the recursive and private method serialize_recursive( ) which produce the JSON data using ABAP data as input. Finally the method exports both the JSON data and the instance to the client.

Page 28: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 28 af 47

-SERIALIZE_RECURSIVE( ): instance/private

Figur 22 SERIALIZE_RECURSIVE Signatures 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> type any table , <comp> type any . describe field ia_data type lv_type components lv_comps . "------------------------------------------------------------------* " Table "------------------------------------------------------------------* if lv_type = cl_ABAP_typedescr=>typekind_table . * itab -> array append '[' to me->gt_fragments . assign ia_data to <itab> . lv_lines = lines( <itab> ) . loop at <itab> assigning <comp> . add 1 to lv_index .

Page 29: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 29 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 .

Page 30: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 30 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

Page 31: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 31 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

Figur 23 DESERIALIZE Signature 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*

Page 32: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 32 af 47

* 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 deserialize. DATA: lv_name TYPE string ,lv_fragment TYPE string ,lv_index TYPE i. FIELD-SYMBOLS: <data> TYPE data . * makes no sense to continue without JSON data CHECK iv_JSON_data IS NOT INITIAL. * Get the instance of the class go_object = get_instance( ia_ABAP_data = ea_ABAP_data iv_JSON_data = iv_JSON_data ). * Assign the attribute of reference to ABAP data to a field symbol ASSIGN zcl_JSON_serializer=>gr_ABAP_ref->* TO <data> . * fragments the JSON data into internal table go_object->fragments_JSON_data( EXPORTING iv_JSON_data = iv_JSON_data EXCEPTIONS format_error = 1 ). IF sy-subrc NE 0. RAISE format_error. ENDIF. * if object name is filled we must remove the object name from fragments as * it's not part of the ABAP data but plays the role as name of the ABAP data. IF iv_object_name IS NOT INITIAL. READ TABLE go_object->gt_fragments INDEX 1 INTO lv_fragment. IF lv_fragment NE '{'. RAISE format_error. ENDIF. CONCATENATE '"' iv_object_name '":' INTO lv_name. READ TABLE go_object->gt_fragments INDEX 2 INTO lv_fragment. IF lv_name NE lv_fragment. RAISE format_error. ENDIF. DESCRIBE TABLE go_object->gt_fragments LINES lv_index. READ TABLE go_object->gt_fragments INDEX lv_index INTO lv_fragment. IF lv_fragment NE '}'. RAISE format_error. ENDIF. DELETE go_object->gt_fragments INDEX lv_index. DELETE go_object->gt_fragments INDEX 1. DELETE go_object->gt_fragments INDEX 1. ENDIF. * deserialize in depth using recursion go_object->deserialize_recursive( CHANGING ca_data = ea_ABAP_data EXCEPTIONS format_error = 1 ) .

Page 33: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 33 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 ). * export the instance eo_instance = go_object. ENDMETHOD.

<</LISTING 21>> Serialize supports singleton patterns by calling the get_instance( ) method. After assigning the ABAP data reference the method divide the JSON data into fragments and then calls the recursive and private method deserialize_recursive( ) which produce the ABAP data using JSOB data as input. Finally the method exports both the ABAP data and the instance to the client.

-DESERIALIZE_RECURSIVE( ): instance/private

Figur 24 DESERIALIZE_RECURSIVE Signature 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.

Page 34: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 34 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> type ABAP_compdescr, <atab> type any table, <itab> type standard table , <xtab> type index table, <htab> type hashed table, <stab> type sorted table, <comp> type any . describe field ca_data type lv_type components lv_comps . "Check metadata for the parameter data lo_datadescr ?= cl_ABAP_datadescr=>describe_by_data( ca_data ) . "------------------------------------------------------------------* " Table: supporting all table types "------------------------------------------------------------------* case lo_datadescr->kind. "Data is of type table when cl_ABAP_datadescr=>kind_table. lo_tabledescr ?= cl_ABAP_typedescr=>describe_by_data( ca_data ) . 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>.

Page 35: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 35 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. * single row for table without array tag? if lv_data = '{'.

Page 36: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 36 af 47

create data lr_row like line of <itab>. 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. append <comp> to <itab>. endloop. * array with 1:m rows and with array tag elseif lv_data = '[' or lv_data = lv_name. 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 <itab>. 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. append <comp> to <itab>. 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_index. "INDEX TABLE includes all standard tables and sorted tables "therefore we might not reach this code assign ca_data to <xtab> . 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.

Page 37: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 37 af 47

* 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>. 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 array 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 <xtab>. 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. append <comp> to <xtab>. 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_hashed. assign ca_data to <htab> . read table gt_fragments index 1 into lv_data. concatenate '"' iv_name '": ' into lv_name. translate: lv_name to lower case

Page 38: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 38 af 47

,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>. 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 array 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 <htab>. 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 <htab>. 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_sorted. assign ca_data to <stab> . read table gt_fragments index 1 into lv_data. concatenate '"' iv_name '": ' into lv_name.

Page 39: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 39 af 47

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>. 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 array 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 <stab>. 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. append <comp> to <stab>. 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 others. raise format_error.

Page 40: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 40 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.

Page 41: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 41 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

Page 42: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 42 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

Figur 25 Compile_JSON Signatures 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.

Page 43: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 43 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 type boolean_01 value 0. 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

Figur 26 FRAGMENTS_JSON_DATA Signature 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 >>

Page 44: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 44 af 47

Listing 24: FRAGMENTS_JSON_DATA ( ) *--------------------------------------------------------------------* * This method fragmentate the JSON string into collection/internal * * table of fragments providing easy proces of data * *--------------------------------------------------------------------* method fragments_JSON_data. CONSTANTS: true TYPE boolean_01 VALUE 1 ,false TYPE boolean_01 VALUE 0 . DATA: lv_length TYPE i VALUE 0 ,lv_i TYPE i VALUE 0 ,lv_c TYPE c ,lv_fragment TYPE string ,lv_JSON_string TYPE string ,lv_quotation_block_on TYPE boolean . FIELD-SYMBOLS: <data> 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.

Page 45: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 45 af 47

CONCATENATE lv_fragment lv_c INTO lv_fragment. ELSE. IF lv_fragment IS NOT INITIAL. CONDENSE lv_fragment. 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. lv_quotation_block_on = false. ELSE. lv_quotation_block_on = true. ENDIF. CONCATENATE lv_fragment lv_c INTO lv_fragment. WHEN ':'. "remember that ':' within quotation as value is allowed IF lv_quotation_block_on = true. CONCATENATE lv_fragment lv_c INTO lv_fragment RESPECTING BLANKS. ELSE. CONCATENATE lv_fragment lv_c INTO lv_fragment. CONDENSE lv_fragment. APPEND lv_fragment TO gt_fragments. CLEAR lv_fragment. ENDIF. WHEN OTHERS. CONCATENATE lv_fragment lv_c INTO lv_fragment RESPECTING BLANKS. ENDCASE. ADD 1 TO lv_i. ENDWHILE. endmethod.

<</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 " }

Page 46: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 46 af 47

}

<</LISTING 25>>

Figur 27 Debug Example 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.

Page 47: Json Article

BGS Consulting ApS Kærmindevej 3 3500 Værløse Danmark

Udskrevet: 05-09-2013 Side 47 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 - the authors home page where you find further informations

http://JSON.org/ - JSON intruduction and standard http://JSONlint.com/ - JSON validator http://scn.sap.com - SAP Community Network