Tuesday, December 21, 2010

Herpes Sores On Chest?

A JSON parser in ABAP

When it comes to structured data between different systems, offers himself to the
JSON data format. It supports arrays, hashes, some basic data types for numbers, strings and logical values and allows data objects to nest arbitrarily deeply into each other. The notation is simple and programmers of different languages (C, C + +, C #, Java, JavaScript) in this or a similar form of art. Compared to XML, there is less overhead for defining the structure, so that the essential, the actual data content, a greater presence.
Here are some simple examples:

is a

array of strings. The following
Hash
defines some examples of name-value pairs:
{
"age": 42,
"occupation": "stenographer"
"Smoking": true
} let

course, arrays and hashes any combination, such as a Hash of Arrays (HoA):
{"Adam": ["Cain," "Abel", "Seth"],
"Abraham": ["Ismael", "Isaac" ] "Isaac": ["John", "Esau"]} Since JSON is essentially the notation of data in JavaScript, it is often used in Web applications communication with the server. Parsing in JavaScript layer of a Web application can be done simply with the statement
eval
[1].
 In order to develop the user interface portable and choose the minimum of effort between different backends to be able to recommend a standardized data format such as JSON for both directions of the data transport, for sending as receiving the data. 

Since I have been working recently on a Web application that sends Ajax requests with JSON data to an SAP system and receives from this, I needed the ABAP stack a JSON parser. Since no such apparently in the SAP system does not yet exist, I programmed it myself: the class ZCL_JSON_PARSER can convert a JSON input in a generally typed ABAP data object that can then be searched in the client code or mapped to the application-specific data structures. When mapping tasks such as this, the test-driven development is very efficient: It is because we do not how to do otherwise with side effects - there is virtually no API calls that can so or to go away, and no dependence on the contents of any database tables. We have an input and a depend only on the ouput. It cries out after, the functionality to implement step by step as the fulfillment of test expectations. [3] Before

however, we can write down even a syntactically correct test must be at least the interface of the test method must be set. In our case we have a public method to parse

have certainly looks as follows:
 

methods parse
importing iv_json type string returning
value (es_data) type zut_data
raising
zcx_parse_error.

input is certainly a string. Output is a data object that can be evaluated with ABAP resources and allows arbitrarily deep nesting. For this purpose, we can take take a generic data type that any data objects. The data type can
any
are not able to use, because it represents only an abstraction of the existing data types and the compiler can not say how many bytes of that type are to be allocated. The type ref to data

does not have this problem. He is "a pointer to anything" and requires a known number of bytes: the length of a memory address, usually four bytes.

The above return type is defined in the Data Dictionary. In ABAP, he would be equivalent to the following note:

types: begin of zut_data,
type type c length 1,
data type ref to data,
end of zut_data.

The type code
type
the type of data object Selected, points to the pointer data

. These can be basic data types like String (S), Number (N), Boolean (B), but also composite types such as hash (h) or array (a).

Note that the so-typed interface already contains an important design decision:
The goal of our mapping, a data object, not an instance of a class.
have in the hierarchy of data objects, ABAP objects and instances of classes have no common generic term.

Man, alternatively, a class could have used the definition of the target type in ABAP. That would have certain advantages, for example, would have the type codes can be eliminated [2] - But at the cost of significant performance losses: create has the
CreateDataSource
command significantly better execution times than

object.
For simple data types, we can already write down some tests: test_42 method. data: ls_data type zut_data.
ls_data = go_ref-> parse ('42 ').

* parser recognize '42 'as a figure:
assert_equal (act = type-ls_data
exp =' N ').
 * value of 42 was determined correctly 
_assert_equals (act = ls_data-data
exp = 42 ). ENDMETHOD. * --- A String recognize
test_string method.
data: ls_data type zut_data.
 ls_data = go_ref-> parse ('"abc"'). 

* String is detected
assert_equal (act-type = ls_data
exp = 'S').

* string is read correctly
_assert_equals (act = exp
ls_data-data = 'abc').
 
ENDMETHOD. "Test_string

This is
_assert_equals
a helper method that works essentially like
assert_equal but if
act is a pointer, before he comparison with exp

dereferenced.
If an empty input will be served beginning, to an empty structure to be returned:
* --- No entry -> No output
test_nothing method.
data: ls_data type zut_data.
ls_data = go_ref-> parse ('').
* Empty string is blank value (no exception)
assert_initial (ls_data).
 
ENDMETHOD. "Test_nothing

how is it with composite types - here hashes and arrays, it is natural to map hashes and arrays in ABAP by internal tables with hash or default access for this array is relatively simple.

types: zut_array_tab
type standard table of zut_data.

does what you want: For each array element can indeed turn contain any data object. So the line type of the array again zut_data . hashes are sets of key / value pairs. The key is a string, the value is again an arbitrary data object. A hash element will look as follows then from:

types: begin of zut_hash_element,
key type string
 include zut_data type as value. 
types: end of zut_hash_element.

The hash itself is now in the ABAP sense, a hash table with line type
zut_hash_element
and key column key : types: zut_hash_tab type hashed table of zut_hash_element
with unique key key.
Now we can formulate tests for hashes and arrays, starting with the simplest case:

It is certainly the empty array be detected ( go_ref is the object under test, to meet the test instance of
ZCL_JSON_PARSER
):

 * --- The empty array [] method 
test_empty_array.

data: ls_data type zut_data.

field-symbols: zut_array_tab \u0026lt;lt_array> type.

ls_data = go_ref-> parse ('[]').

* Array type is detected
assert_equal (act-type = ls_data
exp = 'a').

assert_bound (ls_data-data).

assign ls_data-data-> * to \u0026lt;lt_array>.
assert_subrc (act = sy-subrc
msg = 'data object does not have the correct type').

* It has been determined, the empty array
assert_initial (\u0026lt;lt_array>).

ENDMETHOD. "Test_empty_array

The final and complicated test case, parsing a complex nested structure is also designed to show how the resulting structure can be evaluated in ABAP, to the application to extract the required data:

* --- Nested structure
test_complex_nested method.

data: ls_data type zut_data,
lv_array_length
type i. field-symbols: type zut_array_tab \u0026lt;lt_array>, \u0026lt;lt_hash> zut_hash_tab type, \u0026lt;lt_hash2> zut_hash_tab type, type \u0026lt;ls_line> zut_data, \u0026lt;ls_element> type zut_hash_element, \u0026lt;ls_element2> type zut_hash_element.
ls_data = go_ref-> parse (
'[1, {x: [1,2,3], y: {"c": 1}}, "a", true]'
).
 
* array base type is recognized
assert_equal (act-type = ls_data
exp = 'a').

* Outer array View
assign ls_data-data-> * to \u0026lt;lt_array>.

* He has four elements
describe table \u0026lt;lt_array> lv_array_length lines.
assert_equal (lv_array_length
act = exp = 4).
 * position on the second element 
read table \u0026lt;lt_array> assigning \u0026lt;ls_line>
Index 2
assert_equal (act = exp \u0026lt;ls_line>-type = 'h'). assign \u0026lt;ls_line>-data-> * to \u0026lt;lt_hash>.
* The hash (the second element of the external arrays)
* On the key element of View 'y' position
read table \u0026lt;lt_hash> assigning \u0026lt;ls_element>
 table with key key = 'y'. 
assert_subrc (sy-subrc).

* The key element to the 'y' is again a hash
assert_equal (act = exp \u0026lt;ls_element>-type = 'h').
assign \u0026lt;ls_element>-data-> * to \u0026lt;lt_hash2>. * This innermost hash contains the key 'c' is the number 1: read table \u0026lt;lt_hash2> assigning \u0026lt;ls_element2> table with key key = 'c'.
assert_subrc (sy-subrc).
 assert_equal (act-type = \u0026lt;ls_element2> exp = 'N'). 
_assert_equals (\u0026lt;ls_element2> act = exp-data = 1).

ENDMETHOD.

On the draft class
ZCL_JSON_PARSER a few comments: core is the method get_any that reads any JavaScript data object. It is not only on the single public method parse
for the top data object is called, but also at the designated places in arrays and hashes, which leads to recursive calls.
 It pleased me that the class of fully kept free of global variables: It has no attributes at all. The price was only to provide all of methods with a parameter 
cv_pos
that indicates the current position in the string and can be increased by the methods of successful reading of an expression. The calls to the methods of this bit bulkier than they could be: Each method passes by reference the entire string to be parsed and the position in the string when changing parameters. This there are usually export parameters
ev_found
, a flag that indicates whether the part you are looking object was found.

The class can be a bit more than the JSON format: key hashes can be written down without the quotes in JavaScript as they present a valid variable name. And strings can also be limited by single quotes. The only restriction for Strings: Unicode characters in the notation
\\ uXXXX
are not supported.


[1] Because of the theoretical possibility of

code injection attacks is recommended for applications that run in the public Internet, but also in JavaScript -programmed to use a parser that actually evaluated only data objects and executing any JavaScript code. The most popular JavaScript frameworks like Prototype or jQuery contain a JSON parser.
[2] This is Martin Fowler Refactoringmuster: "Replace Type Code with Subclasses.
[3] This is also the reason why often in TDD launches mapping tasks as an example, such a conversion of integers into Roman numbers.


0 comments:

Post a Comment