Skip to content

Instantly share code, notes, and snippets.

@mydoghasworms
Last active February 19, 2023 14:46

Revisions

  1. mydoghasworms revised this gist Jul 26, 2017. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions json_util.abap
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    * Rather use https://gist.github.com/mydoghasworms/4888a832e28491c3fe47
    * The alternative is a better parser although it is not an emmitter)
    *----------------------------------------------------------------------*
    * CLASS json_util DEFINITION
    *----------------------------------------------------------------------*
  2. mydoghasworms created this gist Apr 3, 2012.
    299 changes: 299 additions & 0 deletions json_util.abap
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,299 @@
    *----------------------------------------------------------------------*
    * 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