Hi ABAP developers,
I would like to introduce a new open-source ABAP JSON library we have developed. Why the world needs a new JSON library? I will explain our rationale behind developing this library with its features. In the end it is about having more choices and knowing trade-offs. I would like to thank Medepia IT Consulting for letting this work become open-source with the MIT License.
Table of Contents:
- Reasoning and features
You can generate any custom JSON with this library unlike alternatives. Thus you can easily achieve API compatibility with another JSON server written in another language. Beside providing a serializer and a deserializer, this library defines an intermediate JSON class in ABAP. Further development may enable more JSON utilities based on this JSON representation.
Standard transformation using JSON-XML: https://scn.sap.com/community/abap/blog/2013/01/07/abap-and-json
Manual string manipulation: While it provides flexibility, it is tedious and error prone work. Sometimes it is used together with CL_TREX_JSON_*
These libraries also seek automatic mapping:
Reasoning and features
It is intriguing to me that there was no JSON node representation in ABAP. Let me give examples from other languages:
Working with JSON in dynamic or loosely typed languages is easier since easily modifiable representations for JSON object and array already exists in the standard language:
- Python: dict and list does the job: https://docs.python.org/2/library/json.html#json-to-py-table
- Ruby: hash and array.
- PHP: associative array and array.
In strongly typed languages like ABAP, Java, Go there are two approaches:
- Intermediary objects
- Java: org.json.jsonobject comes to mind.
- Any Class or Type with annotations that define object and variable names in JSON.
- Java: com.google.code.gson annotation @SerializedName http://static.javadoc.io/com.google.code.gson/gson/2.7/com/google/gson/annotations/package-summary.html
- Go: field tag https://golang.org/pkg/encoding/json/#Marshal
Our library has chosen the intermediary representation approach defining the class ZCL_MDP_JSON_NODE.
- It provides flexibility down to JSON spec. This is important because you get the same flexibility as manual string manipulation without errors. So compatibility of your ABAP service or client with any other JSON API becomes possible without string manipulation.
- You can deserialize any JSON string.
- You can know exactly what deserializer will produce when you see a JSON string.
- You don’t need to define intermediary data types just for JSON input/output.
Future ideas for development:
- Intermediary ZCL_MDP_JSON_NODE class enables development of methods like JSON equality checker, beautification of JSON output, checks for spec validity for string and number values.
- The library uses regexes for parsing. Most of the time regex can be a quick solution. However, I think finite-state machines are better suited for parsers in general.
- We will work on this library based on our needs and your suggestions. For example, we can work towards 100% compliance with the JSON specification running edge case tests.
Examples here are in the shortest form to show how easy JSON manipulation can become. There will be more examples at the project repo using other features of the class. JSON node class is easy to understand if you study attributes and methods once.
DATA: l_json_string TYPE STRING.
‘ “books”: [‘
‘ “title_original”: “Kürk Mantolu Madonna”,’
‘ “title_english”: “Madonna in a Fur Coat”,’
‘ “author”: “Sabahattin Ali”,’
‘ “quote_english”: “It is, perhaps, easier to dismiss a man whose face gives no indication of an inner life. And what a pity that is: a dash of curiosity is all it takes to stumble upon treasures we never expected.”,’
‘ “original_language”: “tr”‘
‘ “title_original”: “Записки из подполья”,’
‘ “title_english”: “Notes from Underground”,’
‘ “author”: “Fyodor Dostoyevsky”,’
‘ “quote_english”: “I am alone, I thought, and they are everybody.”,’
‘ “original_language”: “ru”‘
‘ “title_original”: “Die Leiden des jungen Werthers”,’
‘ “title_english”: “The Sorrows of Young Werther”,’
‘ “author”: “Johann Wolfgang von Goethe”,’
‘ “quote_english”: “The human race is a monotonous affair. Most people spend the greatest part of their time working in order to live, and what little freedom remains so fills them with fear that they seek out any and every means to be rid of it.”,’
‘ “original_language”: “de”‘
‘ “title_original”: “The Call of the Wild”,’
‘ “title_english”: “The Call of the Wild”,’
‘ “author”: “Jack London”,’
‘ “quote_english”: “A man with a club is a law-maker, a man to be obeyed, but not necessarily conciliated.”,’
‘ “original_language”: “en”‘
SEPARATED BY cl_abap_char_utilities=>cr_lf .
DATA: l_json_root_object TYPE REF TO zcl_mdp_json_node.
l_json_root_object = zcl_mdp_json_node=>deserialize( json = l_json_string ).
DATA: l_string TYPE STRING.
l_string = l_json_root_object->object_get_child_node( KEY = ‘books’
)->array_get_child_node( INDEX = 1
)->object_get_child_node( KEY = ‘quote_english’ )->VALUE.
WRITE: ‘Quote from the first book: ‘, l_string .
DATA: l_string_1 TYPE STRING.
DATA: l_root_object_node TYPE REF TO zcl_mdp_json_node
,l_books_array_node TYPE REF TO zcl_mdp_json_node
,l_book_object_node TYPE REF TO zcl_mdp_json_node
,l_book_attr_string_node TYPE REF TO zcl_mdp_json_node .
*Create root object
l_root_object_node = zcl_mdp_json_node=>create_object_node( ).
*Create books array
l_books_array_node = zcl_mdp_json_node=>create_array_node( ).
*add books array to root object with key “books”
l_root_object_node->object_add_child_node( child_key = ‘books’ child_node = l_books_array_node ).
*You would probably want to do this in a loop.
*Create book object node
l_book_object_node = zcl_mdp_json_node=>create_object_node( ).
*Add book object to books array
l_books_array_node->array_add_child_node( l_book_object_node ).
l_book_attr_string_node = zcl_mdp_json_node=>create_string_node( ).
l_book_attr_string_node->VALUE = ‘Kürk Mantolu Madonna’.
*Add string to book object with key “title_original”
l_book_object_node->object_add_child_node( child_key = ‘title_original’ child_node = l_book_attr_string_node ).
l_string_1 = l_root_object_node->serialize( ).
DATA: l_string_2 TYPE STRING.
*DATA: l_root_object_node_2 type zcl_mdp_json_node.
*Create same JSON object with one dot(.) and without data definitions using chaining.
l_string_2 = zcl_mdp_json_node=>create_object_node(
)->object_add_child_node( child_key = ‘books’ child_node = zcl_mdp_json_node=>create_array_node(
)->array_add_child_node( child_node = zcl_mdp_json_node=>create_object_node(
)->object_add_child_node( child_key = ‘title_original’ child_node = zcl_mdp_json_node=>create_string_node(
)->string_set_value( VALUE = ‘Kürk Mantolu Madonna’ )
WRITE: / ‘string 1: ‘ , l_string_1.
WRITE: / ‘string 2: ‘ , l_string_2.
Challenge: Try doing these examples with CL_TREX_JSON_*
For more examples please visit GitHub repo.
On a test machine, using JSON string example above(l_json_string) deserializing and serializing again 10000 times takes 2.1 seconds on average. It shouldn’t have any performance problems with general usage. Complete benchmark code will be on the project repo.
DO 10000 TIMES.
EXPORTING json = l_json_string
IMPORTING node = l_jsonnode ).
EXPORTING node = l_jsonnode
IMPORTING json = l_json_string ).
Here is a presentation about this JSON library:
Project code repository:
The library isn’t extensively battle tested as of now. Testing your use case before using it in production is strongly advised. Please report if you encounter any bugs.
If you are just exposing a table as JSON without much modification, it is easier and probably better to use CL_TREX_JSON_*
If you are developing an extensive application and if you want to design your API beautifully, this library is a pleasant option for you.
Thanks for reading.