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.
data: ok_code type sy-ucomm.
class lcl_json_structure definition deferred.
class lcl_hlp definition.
public section.
data: converter 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.
methods: constructor.
methods: create_source_editor.
methods: create_results_editor.
methods: convert.
endclass.
class lcl_json_structure definition.
public section.
types: begin 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.
constants: c_components type string value '&&components&&'.
data: hierarchy type tt_hierarchy.
methods: build_structure importing i_data type ref to data
exporting e_data type string.
private section.
data: current_id type i.
methods check_component
importing
i_comp type abap_compdescr
value(i_data) type ref to data
value(i_parent) type abap_abstypename
i_level type i.
methods check_object
importing
value(i_data) type ref to data
value(i_parent) type abap_abstypename
i_abap_type type ref to cl_abap_structdescr
i_level type i.
methods create_types returning value(r_definition) type string.
methods: get_id returning value(r_id) type i.
methods: display.
methods: get_types returning value(r_types) type string,
init,
get_internal_types
changing
value(c_type) type t_hierarchy.
endclass.
start-of-selection.
data(hlp) = new 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_container( container_name = 'CC_OUTPUT' ) ) .
endif.
endmethod.
method create_source_editor.
if source_editor is initial.
source_editor = new #( parent = new cl_gui_custom_container( container_name = 'CC_INPUT' ) ) .
endif.
endmethod.
method convert.
data: source 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=>generate( json = cl_bcs_convert=>txt_to_string( it_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 1 to current_id.
r_id = current_id.
endmethod.
method build_structure.
init( ).
data: level type i value 0.
data(abap_type) = cast cl_abap_structdescr( cl_abap_structdescr=>describe_by_data_ref( p_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_id( ) ) to hierarchy.
check_object( i_abap_type = abap_type i_level = level i_data = i_data i_parent = '' ).
e_data = create_types( ).
endmethod.
method init.
refresh: hierarchy.
clear current_id.
endmethod.
method display.
cl_demo_output=>display( create_types( ) ).
endmethod.
method check_object.
loop at i_abap_type->components assigning field-symbol(<comp>).
data(field) = |i_data->{ <comp>-name }|.
assign (field) to 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 i value 0.
level = i_level + 1.
try.
data(str_type) = cast cl_abap_structdescr( cl_abap_structdescr=>describe_by_data_ref( p_data_ref = i_data ) ).
check_object( i_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_id( ) ) to hierarchy.
catch cx_root.
try.
data(id) = get_id( ).
data(table_type) = cast cl_abap_tabledescr( cl_abap_tabledescr=>describe_by_data_ref( p_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>[ 1 ] to <test>.
catch cx_root.
append initial line to <tab> assigning <test>.
endtry.
data(table_line_type) = cast cl_abap_structdescr( cl_abap_structdescr=>describe_by_data_ref( p_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_object( i_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_type) = cast cl_abap_elemdescr( cl_abap_elemdescr=>describe_by_data_ref( p_data_ref = <test> ) ).
add 1 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_ref( p_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_ref( p_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.
data: components 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 = |types: begin 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_types( changing c_type = <h> ).
<h>-type_definition = |types: t_{ <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 = |types: tt_{ <h>-name }{ <h>-id } type standard table of t_{ <h>-name }{ <h>-id } with default key.|.
else.
get_internal_types( changing 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 :-)