I'm playing lately with TFS web services in ABAP and as I'm lazy guy I wanted to make my life a bit easier while working with the JSON results.

There is already a nice class /UI2/CL_JSON available which can deserialize JSON and also it can generate a dynamic structure from JSON file but at the end to make programming easier you need to create local or global structure that will be matching the structure of JSON. 

Instead of spending time on creating manually all needed types, I've decided to create a small report that will do the work for me. Once done it will serve for long time.

The prerequisites for this program is to have class /UI2/CL_JSON in latest version that have a method GENERATE in the first place. On the server in which I was developing this program I had to implement two SAPNotes (2526405 ,2629179) in order to get correct results from the mentioned method. Once done the rest was quite easy.

 

 

Always the latest code for this program you'll find on github under this link https://github.com/fidley/JSON2ABAPType.

Here you can find the first version of the code to understand what is happening here, as well as example usage. Of course it's better to use ABAPGit to install the program than to copy&paste it from here, especially that the screen and GUI status is included into Git repository. Also whole refactoring is not yet done but as an alpha version it doesn't look that bad :-)

 


report zjson2abaptype.
dataok_code type sy-ucomm.

class lcl_json_structure definition deferred.
class lcl_hlp definition.
  public section.

    dataconverter      type ref to lcl_json_structure,
          results        type string,
          source_editor  type ref to cl_gui_textedit,
          results_editor type ref to cl_gui_textedit.
    methodsconstructor.
    methodscreate_source_editor.
    methodscreate_results_editor.
    methodsconvert.
endclass.

class lcl_json_structure definition.

  public section.

    typesbegin of t_hierarchy,
             level           type i,
             name            type string,
             table           type abap_bool,
             structure       type abap_bool,
             type            type string,
             lenght          type i,
             decimals        type i,
             absolute_type   type  abap_abstypename,
             parent          type string,
             final_type      type string,
             type_definition type string,
             id              type i,
           end of t_hierarchy,
           tt_hierarchy type standard table of t_hierarchy with default key.
    constantsc_components type string value '&&components&&'.
    datahierarchy type tt_hierarchy.

    methodsbuild_structure importing i_data type ref to data
                             exporting e_data type string.
  private section.
    datacurrent_id type i.
    methods check_component
      importing
        i_comp          type abap_compdescr
        value(i_data)   type ref to data
        value(i_parenttype  abap_abstypename
        i_level         type i.
    methods check_object
      importing
        value(i_data)   type ref to data
        value(i_parenttype  abap_abstypename
        i_abap_type     type ref to cl_abap_structdescr
        i_level         type i.
    methods create_types returning value(r_definitiontype string.
    methodsget_id returning value(r_idtype i.
    methodsdisplay.
    methodsget_types returning value(r_typestype string,
      init,
      get_internal_types
        changing
          value(c_typetype t_hierarchy.
endclass.


start-of-selection.
  data(hlpnew lcl_hlp).
  call screen 0100.


*&---------------------------------------------------------------------*
*&      Module  PBO  OUTPUT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
module pbo output.
  set pf-status 'STATUS_0100'.
  hlp->create_results_editor).
  hlp->create_source_editor).
endmodule.
*&---------------------------------------------------------------------*
*&      Module  PAI  INPUT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
module pai input.
  case ok_code.
    when 'BACK' or 'UP' or 'EXIT'.
      leave program.
    when '&CONVERT'.
      hlp->convert).
  endcase.
  clear ok_code.
endmodule.

class lcl_hlp implementation.

  method constructor.
    converter new #).
  endmethod.

  method create_results_editor.
    if results_editor is initial.
      results_editor new #parent new cl_gui_custom_containercontainer_name 'CC_OUTPUT' .
    endif.
  endmethod.

  method create_source_editor.
    if source_editor is initial.
      source_editor new #parent new cl_gui_custom_containercontainer_name 'CC_INPUT' .
    endif.
  endmethod.

  method convert.
    datasource type soli_tab.
    source_editor->get_text_as_stream(
      importing
        text                   =  source
      exceptions
        error_cntl_call_method 1
        others                 3
    ).
    if sy-subrc eq 0.
      converter->build_structure(
        exporting
          i_data /ui2/cl_json=>generatejson cl_bcs_convert=>txt_to_stringit_soli   source pretty_name 'Y' )
        importing
          e_data results
      ).

      results_editor->set_textstream(
        exporting
          text                   =  results
        exceptions
          error_cntl_call_method 1
          not_supported_by_gui   2
          others                 3
      ).
      if sy-subrc <> 0.
        message id sy-msgid type sy-msgty number sy-msgno
                   with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
      endif.
    endif.
  endmethod.

endclass.


class lcl_json_structure implementation.
  method get_id.
    add to current_id.
    r_id current_id.
  endmethod.
  method build_structure.
    init).
    datalevel type value 0.
    data(abap_typecast cl_abap_structdescrcl_abap_structdescr=>describe_by_data_refp_data_ref i_data ).
    append value #level level name 'JSON' type abap_type->type_kind absolute_type abap_type->absolute_name structure abap_true id get_idto hierarchy.
    check_objecti_abap_type abap_type i_level level i_data i_data i_parent '' ).
    e_data create_types).
  endmethod.

  method init.

    refreshhierarchy.
    clear current_id.

  endmethod.

  method display.
    cl_demo_output=>displaycreate_types).
  endmethod.
  method check_object.

    loop at i_abap_type->components assigning field-symbol(<comp>).
      data(field|i_data->{ <comp>-name }|.
      assign (fieldto field-symbol(<data>).
      if <data> is assigned and <data> is not initial.

        check_component(
              i_parent i_abap_type->absolute_name
              i_comp <comp>
              i_data <data>
              i_level i_level ).

      endif.
      unassign <data>.
    endloop.

  endmethod.

  method check_component.

    data level type value 0.

    level i_level + 1.

    try.
        data(str_typecast cl_abap_structdescr(  cl_abap_structdescr=>describe_by_data_refp_data_ref  i_data ).

        check_objecti_parent i_parent
                      i_data  i_data
                      i_abap_type str_type
                      i_level     level
                     ).
        append value #level level name i_comp-name type str_type->type_kind absolute_type str_type->absolute_name parent i_parent structure abap_true  id get_idto hierarchy.
      catch cx_root.

        try.
            data(idget_id).
            data(table_typecast cl_abap_tabledescrcl_abap_tabledescr=>describe_by_data_refp_data_ref i_data ).
            append value #level level name i_comp-name type table_type->type_kind absolute_type table_type->absolute_name parent i_parent table  abap_true  id id to hierarchy.

            field-symbols<tab>  type standard table,
                           <test> type any.
            assign i_data->to <tab>.
            try.
                assign <tab>[ to <test>.
              catch cx_root.
                append initial line to <tab> assigning <test>.
            endtry.

            data(table_line_typecast cl_abap_structdescr(  cl_abap_structdescr=>describe_by_data_refp_data_ref <test> ).
            append value #level level name i_comp-name type table_line_type->type_kind absolute_type table_line_type->absolute_name parent table_type->absolute_name structure  abap_true  id id to hierarchy.

            check_objecti_parent table_type->absolute_name
                          i_data  <test>
                          i_abap_type table_line_type
                          i_level     level
                        ).

          catch cx_root.
            "May be a table of strings or integers.
            if table_type is not initial.
              try.
                  if table_type->type_kind eq 'h'.
                    data(table_line_as_el_typecast cl_abap_elemdescrcl_abap_elemdescr=>describe_by_data_refp_data_ref <test> )  ).
                    add to level.
                    append value #level level name i_comp-name type table_line_as_el_type->type_kind absolute_type table_line_as_el_type->absolute_name parent table_line_as_el_type->absolute_name structure  abap_true  id id to hierarchy.
                  endif.
                catch cx_root.
                  data(other_type=  cl_abap_typedescr=>describe_by_data_refp_data_ref  i_data .
                  append value #level level name i_comp-name type other_type->type_kind lenght other_type->length decimals other_type->decimals absolute_type other_type->absolute_name parent i_parent to hierarchy.
              endtry.
            else.
              other_type =  cl_abap_typedescr=>describe_by_data_refp_data_ref  i_data .
              append value #level level name i_comp-name type other_type->type_kind lenght other_type->length decimals other_type->decimals absolute_type other_type->absolute_name parent i_parent to hierarchy.
            endif.
        endtry.
    endtry.

  endmethod.

  method create_types.
    datacomponents type string.
    loop at hierarchy assigning field-symbol(<h>).
      if <h>-structure eq abap_true and <h>-type ne 'g'.
        <h>-final_type |{ <h>-name } type t_{ <h>-name }{ <h>-id }|.
        <h>-type_definition |typesbegin of t_{ <h>-name }{ <h>-id },{ cl_abap_char_utilities=>newline }{ c_components }end of t_{ <h>-name }{ <h>-id }.|.
      elseif <h>-structure eq abap_true.
        <h>-final_type |{ <h>-name } type t_{ <h>-name }{ <h>-id }|.
        get_internal_typeschanging  c_type <h> ).
        <h>-type_definition |typest_{ <h>-name }{ <h>-id type { <h>-absolute_type }.|.
      elseif <h>-table eq abap_true.
        <h>-final_type |{ <h>-name } type tt_{ <h>-name }{ <h>-id }|.
        <h>-type_definition |typestt_{ <h>-name }{ <h>-id type standard table of t_{ <h>-name }{ <h>-id with default key.|.
      else.

        get_internal_typeschanging  c_type <h> ).
      endif.
    endloop.

    loop at hierarchy assigning <h> group by parent <h>-parent ).
      clear components.
      loop at group <h> assigning field-symbol(<g>).
        components components && <g>-final_type && ',' && cl_abap_char_utilities=>newline.
      endloop.
      assign hierarchy[ absolute_type <h>-parent ] to field-symbol(<parent>).
      if sy-subrc eq 0.
        replace all occurrences of c_components in <parent>-type_definition with components.
      endif.
    endloop.

    sort hierarchy by level descending.
    loop at hierarchy assigning <h> where structure eq abap_true
                                       or table eq abap_true.
      r_definition r_definition && <h>-type_definition && cl_abap_char_utilities=>newline.
    endloop.
  endmethod.

  method get_types.
    r_types create_types).
  endmethod.

  method get_internal_types.
    replace first occurrence of regex '\\TYPE-POOL=(.*)\\TYPE=' in c_type-absolute_type with ''.
    if sy-subrc eq 0.
      c_type-final_type |{ c_type-name } type { c_type-absolute_type }|.
      return.
    else.
      replace first occurrence of '\TYPE=' in c_type-absolute_type with ' '.
    endif.

    if c_type-type eq cl_abap_typedescr=>typekind_char.
      c_type-final_type |{ c_type-name } type { c_type-absolute_type } lenght { c_type-lenght }|.
    elseif c_type-type eq cl_abap_typedescr=>typekind_packed.
      c_type-final_type |{ c_type-name } type { c_type-absolute_type } lenght { c_type-lenght } decimals { c_type-decimals }|.
    elseif c_type-type eq cl_abap_typedescr=>typekind_num.
      c_type-final_type |{ c_type-name } type { c_type-absolute_type } lenght { c_type-lenght }|.
    else.
      c_type-final_type |{ c_type-name } type { c_type-absolute_type }|.
    endif.
  endmethod.
endclass.

 

In the example of usage I'll use sample JSON from TFS API documentation

 

{
  "count": 3,
  "value": [
    {
      "id": 297,
      "rev": 1,
      "fields": {
        "System.AreaPath": "Fabrikam-Fiber-Git",
        "System.TeamProject": "Fabrikam-Fiber-Git",
        "System.IterationPath": "Fabrikam-Fiber-Git",
        "System.WorkItemType": "Product Backlog Item",
        "System.State": "New",
        "System.Reason": "New backlog item",
        "System.CreatedDate": "2014-12-29T20:49:20.77Z",
        "System.CreatedBy": "Jamal Hartnett ",
        "System.ChangedDate": "2014-12-29T20:49:20.77Z",
        "System.ChangedBy": "Jamal Hartnett ",
        "System.Title": "Customer can sign in using their Microsoft Account",
        "Microsoft.VSTS.Scheduling.Effort": 8,
        "WEF_6CB513B6E70E43499D9FC94E5BBFB784_Kanban.Column": "New",
        "System.Description": "Our authorization logic needs to allow for users with Microsoft accounts (formerly Live Ids) - http://msdn.microsoft.com/en-us/library/live/hh826547.aspx"
      },
      "url": "https://fabrikam.visualstudio.com/_apis/wit/workItems/297"
    },
    {
      "id": 299,
      "rev": 7,
      "fields": {
        "System.AreaPath": "Fabrikam-Fiber-Git\\Website",
        "System.TeamProject": "Fabrikam-Fiber-Git",
        "System.IterationPath": "Fabrikam-Fiber-Git",
        "System.WorkItemType": "Task",
        "System.State": "To Do",
        "System.Reason": "New task",
        "System.AssignedTo": "Johnnie McLeod ",
        "System.CreatedDate": "2014-12-29T20:49:21.617Z",
        "System.CreatedBy": "Jamal Hartnett ",
        "System.ChangedDate": "2014-12-29T20:49:28.74Z",
        "System.ChangedBy": "Jamal Hartnett ",
        "System.Title": "JavaScript implementation for Microsoft Account",
        "Microsoft.VSTS.Scheduling.RemainingWork": 4,
        "System.Description": "Follow the code samples from MSDN",
        "System.Tags": "Tag1; Tag2"
      },
      "url": "https://fabrikam.visualstudio.com/_apis/wit/workItems/299"
    },
    {
      "id": 300,
      "rev": 1,
      "fields": {
        "System.AreaPath": "Fabrikam-Fiber-Git",
        "System.TeamProject": "Fabrikam-Fiber-Git",
        "System.IterationPath": "Fabrikam-Fiber-Git",
        "System.WorkItemType": "Task",
        "System.State": "To Do",
        "System.Reason": "New task",
        "System.CreatedDate": "2014-12-29T20:49:22.103Z",
        "System.CreatedBy": "Jamal Hartnett ",
        "System.ChangedDate": "2014-12-29T20:49:22.103Z",
        "System.ChangedBy": "Jamal Hartnett ",
        "System.Title": "Unit Testing for MSA login",
        "Microsoft.VSTS.Scheduling.RemainingWork": 3,
        "System.Description": "We need to ensure we have coverage to prevent regressions"
      },
      "url": "https://fabrikam.visualstudio.com/_apis/wit/workItems/300"
    }
  ]
}

 

I will run the program and I'll paste this JSON structure into the text editor and then I'll click on CONVERT button.

 

 

  Once done, I've received the types ready to copy&paste in the second editor window. You'll notice the numbers at the end of the types, these are the internal numbers for structures as there can be several types with the same name in JSON. Types are now opening in the ABAP editor for syntax highlighting. 

 

 

  After pasting the code to Eclipse it looks OK. No errors from syntax check. 

 

  

 

Now you can do some refactoring of the types name if you want or just use type T_JSON1 directly in the DESERIALIZE method. I've tested it with different JSONs and so far the results looks good, but any bugs found by you are welcome :-)