Quantcast
Channel: ABAP Development
Viewing all articles
Browse latest Browse all 948

OO geek journal - ABAP 2 JSON with Composite pattern

$
0
0

This is an alpha version yet, but I am a bit excited to have this done lately, so I decided to post it on SDN.

 

Business background

At my current project at work we need to integrate our web order system with SAP. Among others we use Web Services via SOAP, and do some direct HTTP POSTs from SAP.  Together with my PHP specialist collegue, we decided to exchange data in JSON format. Fortunatelly for him, he has parsers to interpret JSON and build one, unfortunatelly for me we are still on SAP NW 6.4 where no buit-in JSON converter is in place. I know there are some open source projects, but for one reason or another I cannot use them properly. I decided to build mine ABAP to JSON then with OO pattern which seems to fit the requirements. JSON to ABAP will likely to born later, hopefully by utilizing same classes.

 

Note

I am not competing with any of open source ABAP_2_JSON tools. Those projects are most likely more consistent and tested than mine. This post is only intended to give you an insight how we can achieve that with OO approach which I found simplier and more enjoyable to develop.

 

JSON as a tree

While thinking of a JSON format I realized it has tree structure, i.e. following JSON string:

{

    "family": {

        "adults": [

            {

              "father": { "name": "Hommer" }

            },

            {

              "mother": { "name": "Marge" }

            }

        ],

        "children": [

            { "name": "Lisa" },

            { "name": "Bart" },

            { "name": "Maggie", "age": "1" }

        ]

    }

}

 

...can be presented in a tree-like form:

fig JSON tree2.jpg

JSON to Objects

Let's break this down to objects:

  • The lowest level of the tree are simple JSON objects. These are indivisible leaves. Let's call them JSON SIMPLE OBJECT(s).
  • Next level of the left branch we have JSON objects composed of simple leaves. These are composites, namely JSON COMPOSITE OBJECT(s).
  • Another level of the left branch and second last level of the right branch we have JSON arrays consisting of JSON objects (either leaves or composites). These are JSON COMPOSITE ARRAY(s).
  • Next upper level is JSON object of JSON arrays. This is the same type as found in point 2, that is JSON COMPOSITE OBJECT(s).
  • The root is JSON object composed of JSON object(s), so it is JSON COMPOSITE OBJECT too.

 

Relationships

Considering above we therefore have almost all kinds of JSON combinations:

  • JSON SIMPLE OBJECT        <-> a set of key:value pair(s)              i.e. { "name" : "Maggie", "age" : "1" }
  • JSON COMPOSITE OBJECT <-> JSON SIMPLE OBJECT(s)          i.e. { "father" : { } }
  • JSON COMPOSITE OBJECT <-> JSON COMPOSITE OBJECT(s)   i.e. { "family" : { }, ... : { } }
  • JSON COMPOSITE OBJECT <-> JSON COMPOSITE ARRAY(s)    i.e. { "adults" : [ ], "children" : [ ] }
  • JSON COMPOSITE ARRAY  <-> JSON SIMPLE OBJECT(s)          i.e. [ { }, { }, { } ] - right branch
  • JSON COMPOSITE ARRAY  <-> JSON COMPOSITE OBJECT(s)   i.e. [ { }, { } ] - left branch
  • JSON COMPOSITE ARRAY  <-> JSON ARRAY(s) - not presented above but allowed i.e [ [ ], [ ] ]
  • JSON COMPOSITE ARRAY  <-> a set of simple value(s)  - not yet supported   i.e [ "value1", "value2" ]

 

Actually we have all the objects already defined. These are:

  • JSON SIMPLE OBJECT
  • JSON COMPOSITE OBJECT
  • JSON COMPOSITE ARRAY

These can be transformed into classes. Before that, however, let's have a look at well-known design pattern - COMPOSITE, which seems suitable for our case.

 

 

JSON classes with Composite pattern

A composite pattern is used to map objects' relationhips to a tree-like structure. It consists of LEAVES and nodes (COMPOSITES), all of which are tree COMPONENTS. The biggest asset of using this pattern, is that you can treat all of tree objects uniformly, no matter it is a LEAF or a COMPOSITE or a set of them. Additionally COMPOSITES serve the purpose of a container for other COMPONENTS, so we can easily add or remove different COMPONENTS to and from a tree structure.


With this in mind, after bit a tweaking we end up with such UML class diagram for our case:

uml.jpg

Design details

  • In each class I only listed methods which the class implements and the attributes it introduces
  • It is a variation of a Composite pattern to meet JSON specifics
  • JSON SIMPLE OBJECT has same attribute and alike methods as JSON COMPOSITE. This is however misleading at first sign. Refer details below to catch the difference.
  • JSON COMPOSITE was further broken down to JSON COMP OBJECT and JSON COMP ARRAY as there is a slight difference in how they implement their methods (see source code) and how they start/end JSON marks. Also they differ in the way they handle json key. The main method to_json is however common and implemented in JSON COMPOSITE. Details below.

 

Attributes

component_attr.JPG

  • JSON COMPONENT has two attributes which denotes start and end mark of a JSON entity and a bunch of constant. These will be provided in constructor of each class separately. Even JSON SIMPLE OBJECT provides such marks, so we need to have them here too. Please refer constructors below.


Types

  • JSON SIMPLE OBJECT defines components attribute as table string to string pair row.

simple_object_types.JPG

  • The same attribute is defined in the JSON COMPOSITE but with different type. Here it a table with string to JSON COMPONENT row. So we have two different, although same named attributes. For this reason we cannot place them in JSON COMPONENT class.

composite_types.JPG

  • The same applies to add and remove methods. They are called with same name, they even have same purpose, but they play with different row types. We could futher encapsulate this difference within some other class or interface but let's keep it simple now. This might be a topic for another post.

 

Constructors

  • In each constructor we only provide start and end marks appropriate to given JSON entity.

 

     JSON SIMPLE OBJECT and JSON COMP OBJECT

simple_object_constr.JPG

     JSON COMP ARRAY

comp_array_constr.JPG

 

Add method - it simply adds a component to components attribute

 

     JSON SIMPLE OBJECT

simple_object_add.JPG

     JSON COMP OBJECT

  • It plays with different row type compared to JSON SIMPLE OBJECT
  • The key is optional. JSON COMP ARRAY does not accept one, so it is ignored if provided, while JSON COMP OBJECT needs to have one and throws and exception if missing
  • The value is an object passed by value (to avoid any change to component outside the class). It also needs to allow raising an exception, which will be used in JSON COMP OBJECT in case key is not supplied.

comp_object_add.JPG

 

     JSON COMP ARRAY

comp_array_add.JPG

 

Remove method

 

     JSON SIMPLE OBJECT

simple_object_rem.JPG

     JSON COMP OBJECT

  • Again we have two flavors here. One for JSON COMP OBJECT where both via key or value deletion is possible, and one for JSON COMP ARRAY where only removal via value is possible.

comp_object_rem.JPG

     JSON COMP ARRAY

comp_array_rem.JPG

Note

With such small differences in implementation of these methods and fact that JSON format is not likely to change, we could handle object and array in JSON COMPOSITE by passing object type (as one of the constants) to constructor. By using IF or CASE we could distinguish those small differences in all the methods within JSON COMPOSITE. So we might refrain from introducing new subclasses. As we don't however know which way the project might evelove (i.e. it may support to_abap functionality), it is fine to leave the design as-is.

 


to_json method

  • This is the main method which performs convertion to JSON. This one when called, returns a JSON string representing itself + all of its components. This is achieved by delegation to the component, so that each component returns its representation and its components and so on until it reaches bottom of a tree (leaves) where JSON SIMPLE OBJECT has no one to deletage to_json call to.

 

     JSON SIMPLE OBJECT

simple_object_to_json.JPG

 

     JSON COMPOSITE OBJECT

composite_to_json.JPG

 

I share the code as SAPLink nugget available for download on GitHub here. Study it or use it according to your will.


Note

You will need SAPLink plugin MSAG (message class) available for download here.

 

 

Sample usage

Following how we can use the small library by nesting different JSON COMPONENTS:

 

 

report  zac_rep_to_json_example.
data: go_json_simple_obj type ref to zut_cl_json_simple_object,       go_json_comp_obj   type ref to zut_cl_json_comp_object,       go_json_comp_obj2  type ref to zut_cl_json_comp_object,       go_json_comp_arr   type ref to zut_cl_json_comp_array.
data: gv_json type string.
* { "adults" : [] , "children" : [] }
create object go_json_comp_obj.
* left branch
* [ {} , {} ]
create object go_json_comp_arr.
* { "name" : "Hommer" }
create object go_json_simple_obj.
go_json_simple_obj->add( key = 'name' value = 'Hommer' ).
* { "father" : { } }
create object go_json_comp_obj2.
go_json_comp_obj2->add( key = 'father' value = go_json_simple_obj ).
go_json_comp_arr->add( go_json_comp_obj2 ).
* { "name" : "Marge" }
create object go_json_simple_obj.
go_json_simple_obj->add( key = 'name' value = 'Marge' ).
* { "mother" : { } }
create object go_json_comp_obj2.
go_json_comp_obj2->add( key = 'mother' value = go_json_simple_obj ).
go_json_comp_arr->add( go_json_comp_obj2 ).
go_json_comp_obj->add( key = 'adults' value = go_json_comp_arr ).
* right branch
* [ {}, {}, {} ]
create object go_json_comp_arr.
* { "name" : "Lisa" }
create object go_json_simple_obj.
go_json_simple_obj->add( key = 'name' value = 'Lisa' ).
go_json_comp_arr->add( go_json_simple_obj ).
* { "name" : "Maggie", "age" : "1" }
create object go_json_simple_obj.
go_json_simple_obj->add( key = 'name' value = 'Maggie' ).
go_json_simple_obj->add( key = 'age' value = '1' ).
go_json_comp_arr->add( go_json_simple_obj ).
* { "name" : "Bart" }
create object go_json_simple_obj.
go_json_simple_obj->add( key = 'name' value = 'Bart' ).
go_json_comp_arr->add( go_json_simple_obj ).
go_json_comp_obj->add( key = 'children' value = go_json_comp_arr ).
* { "family" : { } }
create object go_json_comp_obj2.
go_json_comp_obj2->add( key = 'family' value = go_json_comp_obj ).
gv_json = go_json_comp_obj2->to_json( ).
write gv_json.

As gv_json is not formatted, it will be truncated when you run it, so I suggest to place a break-point and grab gv_json content from the debugger. Then you can validate it at JSONLint. Please ensure the string will not break its line at the middle of the word first prior to validation.


Note

You can manipulate JSON hierachy by removing node with remove method and by providing:

  • key for JSON SIMPLE OBJECT and JSON COMP OBJECT (for JSON COMP ARRAY you will get a short dump when removing via key, due to exception which inherits from CX_DYNAMIC_CHECK). This is fine as removing via key for a JSON array is more a logical then a syntatical error.
  • value (object) for JSON COMP ARRAY and JSON COMP OBJECT


Table and structure to JSON

With our small API we can now build simple converters which accept flat structure or table on input and convert it to a JSON string. This is also added to a library.

 

And here is sample program for it.

 

report  zac_rep_to_json_example2.
data: go_json type ref to zut_cl_json_component.
data gt_sflight type table of sflight.
data gv_json    type string.
select * from sflight into table gt_sflight up to 2 rows.
go_json = zut_cl_json_utils=>convert_tab_to_json( gt_sflight ).
gv_json = go_json->to_json( ).
write gv_json.

Again I copy gv_json content, paste it to JSONLint, manully correct line breaks and press Validate. This gives nice formatted result:

[

    {

        "MANDT": "100",

        "CARRID": "AA",

        "CONNID": "0017",

        "FLDATE": "20130403",

        "PRICE": "422.94",

        "CURRENCY": "USD",

        "PLANETYPE": "747-400",

        "SEATSMAX": "385",

        "SEATSOCC": "374",

        "PAYMENTSUM": "192708.71",

        "SEATSMAX_B": "31",

        "SEATSOCC_B": "28",

        "SEATSMAX_F": "21",

        "SEATSOCC_F": "21"

    },

    {

        "MANDT": "100",

        "CARRID": "AA",

        "CONNID": "0017",

        "FLDATE": "20130612",

        "PRICE": "422.94",

        "CURRENCY": "USD",

        "PLANETYPE": "747-400",

        "SEATSMAX": "385",

        "SEATSOCC": "371",

        "PAYMENTSUM": "192619.72",

        "SEATSMAX_B": "31",

        "SEATSOCC_B": "30",

        "SEATSMAX_F": "21",

        "SEATSOCC_F": "20"

    }

]

 

Unsupported functionality

This simple library does not fully support JSON functionality. The missing parts are:

  • Some JSON values - each elementary value is interpreted as a string. JSON provides NUMBER, STRING, BOOLEAN, ARRAY, OBJECT, NULL. Here we only have STRING, ARRAY, OBJECT. The NUMBER, BOOLEAN and NULL needs to be converted to string then. This is partly fine as in my business case I use it in simple HTTP POST, which accepts key-value pairs of string-string types. To be compliant with true web applications though, this should however be suplemented. One idea would be introducing new COMPONENT subclass to handle those simple values.
  • Array of elementary values (NUMBER, STRING, BOOLEAN, NULL). This is caused due to above missing part. We can however partly overcome this with creating array of objects.
  • Convertion of nested structures - this could be easily added with extending convert_str_to_json method or achieved by client application by working with loops and passing only flat structures/tables.

 

Afterword

Please don't treat this small library as a out-of-the-box API which you can use for ABAP to JSON converter. The purpose was rather to present you with an ABAP Objects solution by means of a Composite pattern. I won't release it offically unless I use it in couple of my projects, evolve it or get a positive feedback from you.




Viewing all articles
Browse latest Browse all 948

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>