From time to time there are questions how to transform XML data to JSON data. If you search the Web you find quite a bunch of such converters, e.g.,



In those you paste some XML data and get them back as JSON data. The results of these converters are similar. Especially they follow the same conventions regarding which parts of the XML data are converted to JSON objects and which are converted to JSON arrays.


But is this possible in ABAP too? Are there general classes or function modules for that? I don’t know and I didn’t look. Instead I accepted the challenge to write a prototype myself.


Creating such a converter with the help of ABAP might be done in several ways:

  • As an XSLT enthusiast, you use transaction STRANS in order to create an XSLT program that transforms the XML data into valid JSON-XML and feed that into a JSON writer of the sXML library.

  • Another idea could be to write an XSLT or Simple-Transformation that serializes the XML data into into appropriate ABAP data and then you use another XSLT, Simple-Transformation, or ID that serializes these data into valid JSON-XML that you feed into a JSON writer


  • You can use the iXML or sXML libraries to parse the XML data and render the ABAP results into valid JSON-XML.

As an old ABAPer I use the parsing/rendering way by employing the token based parsing and rendering offered by the methods of the sXML library. Here is my code:


    TYPES: BEGIN OF node,

             node_type TYPE if_sxml_node=>node_type,

             name      TYPE string,

             value     TYPE string,

             array     TYPE c LENGTH 1,

           END OF node.

    DATA nodes TYPE TABLE OF node WITH EMPTY KEY.

    DATA(xml_string) = `<root />`.

    DO.

      cl_demo_text=>edit_string(

        EXPORTING

          title = ‘XML-Data’

        CHANGING

          text_string = xml_string

        EXCEPTIONS

          canceled    = 4 ).

      IF sy-subrc = 4.

        EXIT.

      ENDIF.

      DATA(xml) = cl_abap_codepage=>convert_to(

        replace( val = xml_string sub = |\n| with = “ occ = 0  ) ).

      DATA(out) = cl_demo_output=>new(

        )->begin_section( `XML-Data`

        )->write_xml( xml ).

      “Parsing XML into an internal table

      DATA(reader) = cl_sxml_string_reader=>create( xml ).

      CLEAR nodes.

      TRY.

          DO.

            reader->next_node( ).

            IF reader->node_type = if_sxml_node=>co_nt_final.

              EXIT.

            ENDIF.

            APPEND VALUE #(

              node_type  = reader->node_type

              name       = reader->prefix &&

                           COND string(

                             WHEN reader->prefix IS NOT INITIAL

                                  THEN `:` ) && reader->name

              value      = reader->value ) TO nodes.

            IF reader->node_type = if_sxml_node=>co_nt_element_open.

              DO.

                reader->next_attribute( ).

                IF reader->node_type <> if_sxml_node=>co_nt_attribute.

                  EXIT.

                ENDIF.

                APPEND VALUE #(

                  node_type = if_sxml_node=>co_nt_initial

                  name       = reader->prefix &&

                               COND string(

                                 WHEN reader->prefix IS NOT INITIAL

                                   THEN `:` ) && reader->name

                  value      = reader->value ) TO nodes.

              ENDDO.

            ENDIF.

          ENDDO.

        CATCH cx_sxml_parse_error INTO DATA(parse_error).

          out->write_text( parse_error->get_text( ) ).

      ENDTRY.

      “Determine the array limits in the internal table

      LOOP AT nodes ASSIGNING FIELD-SYMBOL(<node_open>)

                    WHERE

                     node_type = if_sxml_node=>co_nt_element_open

                     AND array IS INITIAL.

        DATA(idx_open) = sy-tabix.

        LOOP AT nodes ASSIGNING FIELD-SYMBOL(<node_close>)

                      FROM idx_open  + 1

                      WHERE

                        node_type = if_sxml_node=>co_nt_element_close

                        AND name = <node_open>-name.

          DATA(idx_close) = sy-tabix.

          IF idx_close < lines( nodes ).

            ASSIGN nodes[ idx_close + 1 ] TO FIELD-SYMBOL(<node>).

            IF <node>-node_type = if_sxml_node=>co_nt_element_open AND

               <node>-name = <node_open>-name.

              <node_open>-array = ‘O’.

              <node>-array = ‘_’.

            ELSEIF

              ( <node>-node_type = if_sxml_node=>co_nt_element_open

                AND <node>-name <> <node_open>-name )

              OR <node>-node_type = if_sxml_node=>co_nt_element_close.

              <node_close>-array = COND #(

                WHEN <node_open>-array = ‘O’ THEN ‘C’ ).

              EXIT.

            ENDIF.

          ENDIF.

        ENDLOOP.

      ENDLOOP.

      “Render the internal table to JSON-XML

      DATA(writer) = CAST if_sxml_writer(

       cl_sxml_string_writer=>create( type = if_sxml=>co_xt_json ) ).

                             “create( type = if_sxml=>co_xt_xml10 ) ).

      TRY.

          writer->open_element( name = ‘object’ ).

          LOOP AT nodes ASSIGNING <node>.

            CASE <node>-node_type.

              WHEN if_sxml_node=>co_nt_element_open.

                IF <node>-array IS INITIAL.

                  writer->open_element( name = ‘object’ ).

                  writer->write_attribute( name = ‘name’

                                           value = <node>-name ).

                ELSEIF <node>-array = ‘O’.

                  writer->open_element( name = ‘array’ ).

                  writer->write_attribute( name = ‘name’

                                           value = <node>-name ).

                  writer->open_element( name = ‘object’ ).

                ELSEIF <node>-array = ‘_’.

                  writer->open_element( name = ‘object’ ).

                ENDIF.

              WHEN if_sxml_node=>co_nt_element_close.

                IF <node>-array <> ‘C’.

                  writer->close_element( ).

                ELSE.

                  writer->close_element( ).

                  writer->close_element( ).

                ENDIF.

              WHEN if_sxml_node=>co_nt_initial.

                writer->open_element( name = ‘str’ ).

                writer->write_attribute( name = ‘name’

                                         value = `a_` && <node>-name ).

                writer->write_value( <node>-value ).

                writer->close_element( ).

              WHEN if_sxml_node=>co_nt_value.

                writer->open_element( name = ‘str’ ).

                writer->write_attribute( name = ‘name’

                                         value = `e_` && <node>-name ).

                writer->write_value( <node>-value ).

                writer->close_element( ).

              WHEN OTHERS.

                out->display( ‘A node type is not yet supported’ ).

                RETURN.

            ENDCASE.

          ENDLOOP.

          writer->close_element( ).

          DATA(json) =

            CAST cl_sxml_string_writer( writer )->get_output( ).

          out->next_section( ‘JSON-Data’ ).

          IF writer->if_sxml~type = if_sxml=>co_xt_json.

            out->write_json( json ).

          ELSEIF writer->if_sxml~type = if_sxml=>co_xt_xml10.

            out->write_xml( json ).

          ENDIF.

        CATCH cx_sxml_error INTO DATA(exc).

          out->write( exc->get_text( ) ).

      ENDTRY.

      out->display( ).

    ENDDO.

This example allows you to enter some valid XML into a text edit control and tries to convert it to JSON.


  • Since I have to find the limits of arrays, I cannot parse and render in one go. Therefore I parse the XML data into an internal table nodes first. This part I have mainly taken from my existing sXML parsing example.

  • Then I determine the array limits in the internal table (my poor man’s DOM) for which I have added an own column array. This is the clumsy part of the example. Feel invited to propose a better way!

  • Finally, I render the contents of the internal table to JSON-XML. This is straightforward. Note that I distinguish values that stem from XML attributes and those that come from the XML elements itself with prefixes e_ and a_.


And that’s that.


If you enter XML data, e.g. as follows:


<order number=”4711″ xmlns:demo=”http://www.sap.com/abapdemos“>

<demo:head>

  <demo:status>confirmed</demo:status>

  <demo:date format=”mm‑dd‑yyyy”>07‑19‑2012</demo:date>

</demo:head>

<demo:body>

  <demo:item units=”2″ price=”17.00″>Part No. 0110</demo:item>

  <demo:item units=”1″ price=”10.50″>Part No. 1609</demo:item>

  <demo:item units=”5″ price=”12.30″>Part No. 1710</demo:item>

</demo:body>

</order>


You receive the following JSON data.



{

“order”:

{

  “a_number”:”4711″,

  “demo:head”:

  {

   “demo:status”:

   {

    “e_demo:status”:”confirmed”

   },

   “demo:date”:

   {

    “a_format”:”mm‑dd‑yyyy”,

    “e_demo:date”:”07‑19‑2012″

   }

  },

  “demo:body”:

  {

   “demo:item”:

   [

    {

     “a_units”:”2″,

     “a_price”:”17.00″,

     “e_demo:item”:”Part No. 0110″

    },

    {

     “a_units”:”1″,

     “a_price”:”10.50″,

     “e_demo:item”:”Part No. 1609″

    },

    {

     “a_units”:”5″,

     “a_price”:”12.30″,

     “e_demo:item”:”Part No. 1710″

    }

   ]

  }

}

}


The result is at least similar to those of the above mentioned online converters, yay!


If you create an XML writer instead of the JSON writer in the above code with create( type = if_sxml=>co_xt_xml10 ), you receive and display the JSON-XML.


The program is far from being bullet-proof. Just an example, tested with some harmless XML data. I restricted the conversion to textual data (no numbers in JSON, no handling of raws) and other things might be missing to. Be invited to find errors and gaps and propose solutions. Maybe there is even an official tool available?


Cheers!


Horst


PS:


Be aware, that there is not one standard way of converting arbitrary XML data to JSON data. Or is there?


The conversions shown here are based on some conventions. In real life examples the desired results might look different and depend on the semantical data structure based on XSD, XML schema or whatsoever. I would expect that in most (business) use cases transformations from XML to JSON are rather specific than general.




To report this post you need to login first.

9 Comments

You must be Logged on to comment or reply to a post.

  1. Ilmir Nuriev

    Hello. Here is the way to create a json like easier.

    Sorry if I climb up there 🙂

    For example

        TYPES:

        BEGIN OF ty_id,

        id TYPE string,

        value    TYPE string,

        END OF ty_id.

        DATA: lt_temp TYPE TABLE OF ty_id,

              ls_temp LIKE LINE OF lt_temp.

        TYPES:

        BEGIN OF ty_tab,

          id TYPE string,

        project_id TYPE string,

        tracker_id TYPE string,

        status_id   TYPE string,

        priority_id TYPE string,

        autor_id TYPE string,

        assigned_to_id TYPE string,

        subject  TYPE string, “temp”,

        description TYPE string,

         due_date TYPE string,

         custom_fields LIKE lt_temp,

        END OF ty_tab.

        DATA: ls_issue TYPE ty_tab,

              writer TYPE REF TO cl_sxml_string_writer,

              json TYPE xstring,

              json_string TYPE string.

        FIELD-SYMBOLS: <fs_temp> LIKE LINE OF ls_issue-custom_fields.

        writer = cl_sxml_string_writer=>create(   type = if_sxml=>co_xt_json

           normalizing = abap_true

             ).

        ls_issue-project_id = ‘6’.

        ls_issue-tracker_id = ’34’.

        ls_issue-status_id = ‘1’.

        ls_issue-priority_id = ‘4’.

    *

        ls_issue-autor_id = ‘199’.

        ls_issue-assigned_to_id = ‘199’.

        ls_issue-subject =   |Mistake:{ I_FS_UP-mistake } Date:({ I_FS_UP-data_vrem(4) }:{ I_FS_UP-data_vrem+4(2) }:{ I_FS_UP-data_vrem+6(2) }) | &

                          |Time:({ I_FS_UP-data_vrem+8(2) }:{ I_FS_UP-data_vrem+10(2) }:{ I_FS_UP-data_vrem+12(2) })|.

        ls_issue-description = |*Short text*| &

                               |\n{ I_FS_UP-kap0 } | &&

                               |\n| &&

                               |\n*. What is it?*| &&

                               |\n{ I_FS_UP-kap1 }| &&

                               |\n| &&

                               |\n*. Avistake*| &&

                               |\n{ I_FS_UP-kap3 }|.

    *

    *Tracker_id =34, project_id = 6

        APPEND INITIAL LINE TO ls_issue-custom_fields ASSIGNING <fs_temp>.

        <fs_temp>-id = ‘1’.

        <fs_temp>-value = i_fs_up-ztransaction.

        APPEND INITIAL LINE TO ls_issue-custom_fields ASSIGNING <fs_temp>.

        <fs_temp>-id = ’72’.

        <fs_temp>-value = i_fs_up-categor.

        APPEND INITIAL LINE TO ls_issue-custom_fields ASSIGNING <fs_temp>.

        <fs_temp>-id = ’73’.

        <fs_temp>-value = i_fs_up-zuser.

        APPEND INITIAL LINE TO ls_issue-custom_fields ASSIGNING <fs_temp>.

        <fs_temp>-id = ’74’.

        <fs_temp>-value = i_fs_up-ztransaction_id.

        APPEND INITIAL LINE TO ls_issue-custom_fields ASSIGNING <fs_temp>.

        <fs_temp>-id = ’76’.

        <fs_temp>-value = i_fs_up-kap2.

        APPEND INITIAL LINE TO ls_issue-custom_fields ASSIGNING <fs_temp>.

        <fs_temp>-id = ’77’.

        <fs_temp>-value = i_fs_up-kap4.

        APPEND INITIAL LINE TO ls_issue-custom_fields ASSIGNING <fs_temp>.

        <fs_temp>-id = ’78’.

        <fs_temp>-value = i_fs_up-kap7.

        CALL TRANSFORMATION id SOURCE issue =  ls_issue

                           RESULT XML writer.

        json = writer->get_output( ).

        CALL FUNCTION ‘ECATT_CONV_XSTRING_TO_STRING’

          EXPORTING

            im_xstring  = json

    *       im_encoding = ‘UTF-8’

          IMPORTING

            ex_string   = json_string.

        TRANSLATE json_string TO LOWER CASE.

        r_result = json_string.

    The result json in place.

    (0) 
    1. Horst Keller Post author

      Now that’s far too simple 😉

      You convert ABAP data (not XML data) with transformation ID to asJSON.

      I have that kind of examples in the documentation since long (e.g. asJSON for internal tables, but see also JSON, Render).

      The task is to convert any XML data to JSON.

      If you read any XML into your program, transform that to ABAP data and then those back to JSON, then … (that would be the second way listed in the blog above).

      PS:

      CL_ABAP_CODE_PAGE would be the better choice compared to function module ‘ECATT_CONV_XSTRING_TO_STRING’.

      (0) 
  2. Peter Inotai

    Hello Horst,

    Very interesting, thanks!

    Do you know the minimum system requirements for your demo program? I tried on 7.40 SP 7, but cl_demo_text=>edit_string( ) doesn’t exit. 🙁

    Thanks,

    Peter

    (0) 
    1. Horst Keller Post author

      but cl_demo_text=>edit_string( ) doesn’t exit.

      In  7.50 it’s there. But the text input isn’t the point of the demo. You can replace it by any method you like.


      Horst


      (0) 
  3. Sandra Rossi

    Nice job 🙂

    I don’t see any comment about the problem of “arrays” containing only one line in the XML. It’s impossible to detect it without knowing the XSD for instance. It could be nice to add a little part in the code to choose the nodes which are arrays (for instance a table of XPATH-like paths).

    (0) 
    1. Horst Keller Post author

      Yep, the task of the example is rather to show the limitations of such a general approach and not providing something really useful. Hopefully, people asking “Hello, need function to convert XML to JSON” will learn that it is not such a trivial task.

      (0) 
      1. Rüdiger Plantiko

        At least they should learn that “Hello, I want to transform this particular XML document into this particular JSON / into this particular ABAP data structure” is the appropriate question.

        (0) 

Leave a Reply