Skip to content

Instantly share code, notes, and snippets.

@mydoghasworms
Last active February 19, 2023 14:46
Show Gist options
  • Save mydoghasworms/2291540 to your computer and use it in GitHub Desktop.
Save mydoghasworms/2291540 to your computer and use it in GitHub Desktop.
ABAP Utility class for mapping JSON to ABAP data and vice versa - Rather use https://gist.github.com/mydoghasworms/4888a832e28491c3fe47
* Rather use https://gist.github.com/mydoghasworms/4888a832e28491c3fe47
* The alternative is a better parser although it is not an emmitter)
*----------------------------------------------------------------------*
* CLASS json_util DEFINITION
*----------------------------------------------------------------------*
class json_util definition.
public section.
class-methods:
data_to_json importing data type any
returning value(json) type string,
json_to_data importing json type string
changing data type any.
endclass. "json_util DEFINITION
*----------------------------------------------------------------------*
* CLASS json_util IMPLEMENTATION
*----------------------------------------------------------------------*
class json_util implementation.
method data_to_json.
data: lr_desc type ref to cl_abap_typedescr.
data: lr_elem type ref to cl_abap_elemdescr.
data: lr_sdes type ref to cl_abap_structdescr.
data: ls_comp type cl_abap_structdescr=>component.
data: lt_comp type cl_abap_structdescr=>component_table.
data: lv_json type string.
data: lv_field type string.
data: lv_value type text255.
field-symbols: <field> type any.
data: lt_x031l type dd_x031l_table.
data: ls_x031l type x031l.
data: ls_dfies type dfies.
data: lv_meth(30) type c value 'GET_DDIC_FIELD'.
data: lv_date type d.
data: lv_date_c(10) type c.
data: lv_time type t.
data: lv_time_c(8) type c.
data: lv_tabix type i.
data: lv_index type i.
data: lv_passed1st type boole_d.
lr_desc = cl_abap_typedescr=>describe_by_data( data ).
case lr_desc->type_kind.
when cl_abap_typedescr=>typekind_struct1 or cl_abap_typedescr=>typekind_struct2.
json = '{'.
* Use RTTI to discover structure members and process them individually
lr_sdes ?= lr_desc.
lt_comp = lr_sdes->get_components( ).
loop at lt_comp into ls_comp.
assign component ls_comp-name of structure data
to <field>.
if sy-subrc = 0 and <field> is not initial.
* For consecutive elements, add a comma separator after the previous value
if lv_passed1st = 'X'.
concatenate json ',' into json.
endif.
lv_json = data_to_json( data = <field> ).
concatenate json ' "' ls_comp-name '": ' lv_json into json.
lv_passed1st = 'X'.
endif.
endloop.
concatenate json '}' into json.
when cl_abap_typedescr=>typekind_table.
data: ld_line type ref to data.
field-symbols: <tab> type any table.
field-symbols: <line> type any.
assign data to <tab>.
create data ld_line like line of <tab>.
assign ld_line->* to <line>.
* Open array for table entries
json = '['.
loop at <tab> into <line>.
lv_json = data_to_json( data = <line> ).
concatenate json lv_json into json separated by space.
at last.
continue.
endat.
* Separate consecutive values by commas
concatenate json ',' into json.
endloop.
* Close array for table entries
concatenate json ']' into json separated by space.
when cl_abap_typedescr=>typekind_dref.
* For data references, dereference the data and call method again
field-symbols: <data> type any.
assign data->* to <data>.
if sy-subrc = 0.
json = data_to_json( data = <data> ).
else.
json = '{}'. "Will produce empty JS object
endif.
when others.
* For elementary types, we merely return a text representation of the value
json = data.
condense json.
* Escape special characters
replace all occurrences of '\' in json with '\\'.
replace all occurrences of '"' in json with '\"'.
replace all occurrences of cl_abap_char_utilities=>newline in json with '\n'.
replace all occurrences of cl_abap_char_utilities=>horizontal_tab in json with '\t'.
replace all occurrences of cl_abap_char_utilities=>form_feed in json with '\f'.
replace all occurrences of cl_abap_char_utilities=>vertical_tab in json with '\v'.
replace all occurrences of cl_abap_char_utilities=>backspace in json with '\b'.
* Numeric values do not need to be escaped
if lr_desc->type_kind ne cl_abap_typedescr=>typekind_num and
lr_desc->type_kind ne cl_abap_typedescr=>typekind_packed and
lr_desc->type_kind ne cl_abap_typedescr=>typekind_float and
lr_desc->type_kind ne cl_abap_typedescr=>typekind_int and
lr_desc->type_kind ne cl_abap_typedescr=>typekind_int1 and
lr_desc->type_kind ne cl_abap_typedescr=>typekind_int2.
concatenate '"' json '"' into json.
endif.
endcase.
endmethod. "data_to_json
method json_to_data.
data: lv_off type i.
data: lv_len type i.
data: lv_key type string.
data: lv_value type string.
data: lv_char type char1. "Current chacater
data: lv_pchar type char1. "Previous character
data: lv_instr type boole_d. "Indicator: cursor in string
data: lv_level type i. "Depth inside a block
data: lv_table type boole_d.
field-symbols: <fk> type string.
field-symbols: <data> type any.
field-symbols: <table> type any table.
field-symbols: <sotab> type sorted table.
field-symbols: <sttab> type standard table.
field-symbols: <line> type any.
data: ls_line type ref to data.
data: lr_td type ref to cl_abap_typedescr.
data: lr_ttd type ref to cl_abap_tabledescr.
* If the incoming json contains no '{}[]', we are dealing with
* a basic (scalar) value that is simply assigned to the data
* and then we return
if json na '{}[]'.
* Replace escape characters (TODO: Check if there are more!)
lv_value = json.
replace all occurrences of '\n' in lv_value with cl_abap_char_utilities=>newline.
replace all occurrences of '\t' in lv_value with cl_abap_char_utilities=>horizontal_tab.
replace all occurrences of '\f' in lv_value with cl_abap_char_utilities=>form_feed.
replace all occurrences of '\v' in lv_value with cl_abap_char_utilities=>vertical_tab.
replace all occurrences of '\b' in lv_value with cl_abap_char_utilities=>backspace.
replace all occurrences of '\\' in lv_value with '\'.
* TODO: Deal with specific data types, e.g. dates etc.
data = lv_value.
exit.
endif.
lv_len = strlen( json ).
* Check if we are dealing with a table
lr_td = cl_abap_typedescr=>describe_by_data( data ).
if lr_td->type_kind = cl_abap_typedescr=>typekind_table.
* This information is used later...
lv_table = 'X'.
assign data to <table>.
create data ls_line like line of <table>.
assign ls_line->* to <line>.
else.
lv_table = ' '.
endif.
* Reset counters/flags
lv_off = 0.
lv_instr = ' '.
lv_level = 0.
assign lv_key to <fk>.
while lv_off < lv_len.
lv_char = json+lv_off(1).
**********************************************************************
* IN STRING
**********************************************************************
* Character is in a string delimited by double quotes
if lv_instr = 'X'.
if lv_char = '"'.
* Switch out of delimited character string
if lv_pchar ne '\'.
lv_instr = ' '.
else.
concatenate <fk> lv_char into <fk> respecting blanks.
endif.
else.
concatenate <fk> lv_char into <fk> respecting blanks.
endif.
**********************************************************************
* OUTSIDE STRING
**********************************************************************
* Character is not in a string delimited by double quotes
else.
* On opening character, shift level up
if lv_char ca '{['.
add 1 to lv_level.
endif.
* When the value is contained in a {}/[], the entire value must
* be passed to the next level of processing
if lv_level > 1.
concatenate <fk> lv_char into <fk> respecting blanks.
else.
if lv_char ca '[{'. "<- Chars ignored outside of str
elseif lv_char = ':'.
assign lv_value to <fk>.
* The key collected up to now is assigned to the data member
translate lv_key to upper case.
shift lv_key left deleting leading space.
assign component lv_key of structure data to <data>.
* End of a key/value pair (we bump up against delimiter) - pass to next level
elseif ( lv_char = ',' or lv_char = '}' ) and lv_table = space.
* Process collected value
shift lv_value left deleting leading space.
json_to_data( exporting json = lv_value changing data = <data> ).
* Clear key and value
clear: lv_key, lv_value.
assign lv_key to <fk>.
clear: lv_key, lv_value.
* End of a key/value pair (we bump up against delimiter) - pass to next level
* But in table mode, we pass an instance of a row of the table, and afterward
* add it to the table
elseif ( lv_char = ',' or lv_char = ']' ) and lv_table = 'X'.
* Process collected value
* Inside array in JSON, there are no keys, only list of values, the collected
* value is in lv_key
shift lv_key left deleting leading space.
json_to_data( exporting json = lv_key changing data = <line> ).
* On return: if dealing with table, add the record to the table
lr_ttd ?= lr_td.
if lr_ttd->table_kind = cl_abap_tabledescr=>tablekind_sorted
or lr_ttd->table_kind = cl_abap_tabledescr=>tablekind_hashed.
assign data to <sotab>.
insert <line> into table <sotab>.
else.
assign data to <sttab>.
append <line> to <sttab>.
endif.
clear <line>.
* Clear key and value
clear: lv_key, lv_value.
assign lv_key to <fk>.
clear: lv_key, lv_value.
* Switch cursor into delimited string; consecutive characters
* are then treated as part of a key or value, even if they are
* special characters
elseif lv_char = '"'.
lv_instr = 'X'.
* Other chars processed as either key or value
else.
concatenate <fk> lv_char into <fk> respecting blanks.
endif.
endif.
* On closing character, shift level down again
if lv_char ca '}]'.
subtract 1 from lv_level.
endif.
* END: Are we in string or out?
endif.
lv_pchar = lv_char.
add 1 to lv_off.
endwhile.
endmethod. "json_to_data
endclass. "json_util IMPLEMENTATION
@konijn
Copy link

konijn commented Sep 3, 2013

Greetings,

line 153 : if json na '{}[]'.

Completely fails when we are expecting a string , and that string contains any of those characters.

I think you will also need to check the value of p_abap ( DESCRIBE ), and if it is a string or char, then regardless of those characters, you are dealing with a scalar.

Other than, I have used your code for close to a year, it never broke till now.

@atmosema
Copy link

atmosema commented Oct 7, 2013

Hi,

if a structure includes another structure you need to insert code between line 48 and 49 to extract the components of each structure recursively and make sure that gt_comp has all fields like SE11 expands the .INCLUDE structures.

also if a numeric value is processed and has leading zeros I would suggest to call this FM like here (line #116):

() Numeric values do not need to be escaped
IF lo_desc->type_kind NE cl_abap_typedescr=>typekind_num AND
lo_desc->type_kind NE cl_abap_typedescr=>typekind_packed AND
lo_desc->type_kind NE cl_abap_typedescr=>typekind_float AND
lo_desc->type_kind NE cl_abap_typedescr=>typekind_int AND
lo_desc->type_kind NE cl_abap_typedescr=>typekind_int1 AND
lo_desc->type_kind NE cl_abap_typedescr=>typekind_int2.
CONCATENATE '"' rv_json '"' INTO rv_json.
ELSE.
(
) Eliminate leading zeros for numeric values
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_OUTPUT'
EXPORTING
input = rv_json
IMPORTING
output = rv_json.
ENDIF.

Cheers,
Martin

@MdAhmadAli
Copy link

hey please help me out i am new in abap .
How to get field from json format webervice into abap code.

@pallavigit
Copy link

hi i need to consume the rest web service through abap program... I have all the details for the WSDL URL or rest web service which is created in cake PHP .Now i need to send some data in structure through our abap code ... i stuck in this process please let me know the solution.. i will be very grateful to you guys ...thanks pallavi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment