ZCL_JSON源码*----------------------------------------------------------------------*
* CLASS zcl_json DEFINITION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS zcl_json DEFINITION.
PUBLIC SECTION.
TYPE-POOLS abap .
CLASS cl_abap_tstmp DEFINITION LOAD .
CLASS cx_sy_conversion_error DEFINITION LOAD .
TYPES:
json TYPE string,
BEGIN OF name_mapping,
abap TYPE abap_compname,
json TYPE string,
END OF name_mapping,
name_mappings TYPE HASHED TABLE OF name_mapping WITH UNIQUE KEY abap,
ref_tab TYPE STANDARD TABLE OF REF TO data WITH DEFAULT KEY,
bool TYPE char1,
tribool TYPE char1 ,
pretty_name_mode TYPE char1 .
CONSTANTS:
BEGIN OF pretty_mode,
none TYPE char1 VALUE ``,
low_case TYPE char1 VALUE `L`,
camel_case TYPE char1 VALUE `X`,
extended TYPE char1 VALUE `Y`,
user TYPE char1 VALUE `U`,
user_low_case TYPE char1 VALUE `C`,
END OF pretty_mode,
BEGIN OF c_bool,
true TYPE bool VALUE `X`,
false TYPE bool VALUE ``,
END OF c_bool ,
BEGIN OF c_tribool,
true TYPE tribool VALUE c_bool-true,
false TYPE tribool VALUE `-`,
undefined TYPE tribool VALUE ``,
END OF c_tribool,
mc_key_separator TYPE string VALUE `-`, "#EC NOTEXT
version TYPE i VALUE 16.
CLASS-DATA sv_white_space TYPE string READ-ONLY .
CLASS-DATA mc_bool_types TYPE string READ-ONLY VALUE `\TYPE-POOL=ABAP\TYPE=ABAP_BOOL\TYPE=BOOLEAN\TYPE=BOOLE_D\TYPE=XFELD`. "#EC NOTEXT
CLASS-DATA mc_bool_3state TYPE string READ-ONLY VALUE `\TYPE=BOOLEAN`. "#EC NOTEXT
CLASS-DATA mc_json_type TYPE string READ-ONLY .
CLASS-METHODS class_constructor .
CLASS-METHODS string_to_xstring
IMPORTING
in TYPE string
CHANGING
VALUE(out) TYPE any .
CLASS-METHODS xstring_to_string
IMPORTING
in TYPE any
RETURNING
VALUE(out) TYPE string .
CLASS-METHODS raw_to_string
IMPORTING
iv_xstring TYPE xstring
iv_encoding TYPE abap_encoding OPTIONAL
RETURNING
VALUE(rv_string) TYPE string .
CLASS-METHODS string_to_raw
IMPORTING
iv_string TYPE string
iv_encoding TYPE abap_encoding OPTIONAL
RETURNING
VALUE(rv_xstring) TYPE xstring .
CLASS-METHODS dump
IMPORTING
data TYPE data
compress TYPE bool DEFAULT c_bool-false
type_descr TYPE REF TO cl_abap_typedescr OPTIONAL
pretty_name TYPE pretty_name_mode DEFAULT pretty_mode-none
assoc_arrays TYPE bool DEFAULT c_bool-false
ts_as_iso8601 TYPE bool DEFAULT c_bool-false
RETURNING
VALUE(r_json) TYPE json .
CLASS-METHODS deserialize
IMPORTING
json TYPE json OPTIONAL
jsonx TYPE xstring OPTIONAL
pretty_name TYPE pretty_name_mode DEFAULT pretty_mode-none
assoc_arrays TYPE bool DEFAULT c_bool-false
assoc_arrays_opt TYPE bool DEFAULT c_bool-false
name_mappings TYPE name_mappings OPTIONAL
conversion_exits TYPE bool DEFAULT c_bool-false
hex_as_base64 TYPE bool DEFAULT c_bool-true
CHANGING
data TYPE data .
CLASS-METHODS serialize
IMPORTING
data TYPE data
compress TYPE bool DEFAULT c_bool-false
name TYPE string OPTIONAL
pretty_name TYPE pretty_name_mode DEFAULT pretty_mode-none
type_descr TYPE REF TO cl_abap_typedescr OPTIONAL
assoc_arrays TYPE bool DEFAULT c_bool-false
ts_as_iso8601 TYPE bool DEFAULT c_bool-false
expand_includes TYPE bool DEFAULT c_bool-true
assoc_arrays_opt TYPE bool DEFAULT c_bool-false
numc_as_string TYPE bool DEFAULT c_bool-false
name_mappings TYPE name_mappings OPTIONAL
conversion_exits TYPE bool DEFAULT c_bool-false
format_output TYPE bool DEFAULT c_bool-false
hex_as_base64 TYPE bool DEFAULT c_bool-true
RETURNING
VALUE(r_json) TYPE json .
METHODS deserialize_int
IMPORTING
json TYPE json OPTIONAL
jsonx TYPE xstring OPTIONAL
CHANGING
data TYPE data
RAISING
cx_sy_move_cast_error .
CLASS-METHODS generate
IMPORTING
json TYPE json
pretty_name TYPE pretty_name_mode DEFAULT pretty_mode-none
name_mappings TYPE name_mappings OPTIONAL
RETURNING
VALUE(rr_data) TYPE REF TO data .
METHODS serialize_int
IMPORTING
data TYPE data
name TYPE string OPTIONAL
type_descr TYPE REF TO cl_abap_typedescr OPTIONAL
RETURNING
VALUE(r_json) TYPE json .
METHODS generate_int
IMPORTING
json TYPE json
VALUE(length) TYPE i OPTIONAL
CHANGING
data TYPE REF TO data
offset TYPE i DEFAULT 0
RAISING
cx_sy_move_cast_error .
METHODS constructor
IMPORTING
compress TYPE bool DEFAULT c_bool-false
pretty_name TYPE pretty_name_mode DEFAULT pretty_mode-none
assoc_arrays TYPE bool DEFAULT c_bool-false
ts_as_iso8601 TYPE bool DEFAULT c_bool-false
expand_includes TYPE bool DEFAULT c_bool-true
assoc_arrays_opt TYPE bool DEFAULT c_bool-false
strict_mode TYPE bool DEFAULT c_bool-false
numc_as_string TYPE bool DEFAULT c_bool-false
name_mappings TYPE name_mappings OPTIONAL
conversion_exits TYPE bool DEFAULT c_bool-false
format_output TYPE bool DEFAULT c_bool-false
hex_as_base64 TYPE bool DEFAULT c_bool-true
bool_types TYPE string DEFAULT mc_bool_types
bool_3state TYPE string DEFAULT mc_bool_3state
initial_ts TYPE string DEFAULT `""`
initial_date TYPE string DEFAULT `""`
initial_time TYPE string DEFAULT `""` .
CLASS-METHODS bool_to_tribool
IMPORTING
iv_bool TYPE bool
RETURNING
VALUE(rv_tribool) TYPE tribool .
CLASS-METHODS tribool_to_bool
IMPORTING
iv_tribool TYPE tribool
RETURNING
VALUE(rv_bool) TYPE bool .
PROTECTED SECTION.
TYPES:
BEGIN OF t_s_field_cache,
name TYPE string,
type TYPE REF TO cl_abap_datadescr,
elem_type TYPE REF TO cl_abap_elemdescr,
convexit_out TYPE string,
convexit_in TYPE string,
value TYPE REF TO data,
END OF t_s_field_cache ,
BEGIN OF t_s_symbol,
header TYPE string,
compressable TYPE abap_bool,
read_only TYPE abap_bool.
INCLUDE TYPE t_s_field_cache.
TYPES: END OF t_s_symbol ,
t_t_symbol TYPE STANDARD TABLE OF t_s_symbol WITH DEFAULT KEY ,
t_t_field_cache TYPE HASHED TABLE OF t_s_field_cache WITH UNIQUE KEY name ,
name_mappings_ex TYPE HASHED TABLE OF name_mapping WITH UNIQUE KEY json .
TYPES:
BEGIN OF t_s_name_value,
name TYPE string,
value TYPE json,
END OF t_s_name_value .
TYPES:
t_t_name_value TYPE SORTED TABLE OF t_s_name_value WITH UNIQUE KEY name ,
t_t_json TYPE STANDARD TABLE OF json WITH DEFAULT KEY .
TYPES:
BEGIN OF t_s_struct_type,
keys TYPE string,
type TYPE REF TO cl_abap_datadescr,
END OF t_s_struct_type .
TYPES:
t_t_struct_type TYPE SORTED TABLE OF t_s_struct_type WITH UNIQUE KEY keys ,
BEGIN OF t_s_struct_cache_res,
data TYPE REF TO data,
symbols TYPE t_t_symbol,
END OF t_s_struct_cache_res ,
BEGIN OF t_s_struct_cache,
type_descr TYPE REF TO cl_abap_structdescr,
include_aliases TYPE abap_bool,
level TYPE i,
result TYPE t_s_struct_cache_res,
END OF t_s_struct_cache ,
t_t_struct_cache TYPE HASHED TABLE OF t_s_struct_cache WITH UNIQUE KEY type_descr include_aliases level .
CONSTANTS mc_default_indent TYPE string VALUE ` `. "#EC NOTEXT
DATA mv_bool_types TYPE string.
DATA mv_bool_3state TYPE string.
DATA mv_initial_ts TYPE string VALUE `""`. "#EC NOTEXT
DATA mv_initial_date TYPE string VALUE `""`. "#EC NOTEXT
DATA mv_initial_time TYPE string VALUE `""`. "#EC NOTEXT
DATA mv_compress TYPE bool .
DATA mv_pretty_name TYPE pretty_name_mode .
DATA mv_assoc_arrays TYPE bool .
DATA mv_ts_as_iso8601 TYPE bool .
DATA mv_expand_includes TYPE bool .
DATA mv_assoc_arrays_opt TYPE bool .
DATA mv_strict_mode TYPE bool .
DATA mv_numc_as_string TYPE bool .
DATA mv_format_output TYPE bool .
DATA mv_conversion_exits TYPE bool .
DATA mv_hex_as_base64 TYPE bool .
DATA mt_name_mappings TYPE name_mappings .
DATA mt_name_mappings_ex TYPE name_mappings_ex .
DATA mt_struct_type TYPE t_t_struct_type .
DATA mt_struct_cache TYPE t_t_struct_cache .
CLASS-DATA mc_name_symbols_map TYPE string VALUE ` _/_\_:_;_~_._,_-_+_=_>_<_|_(_)_[_]_{_}_@_+_*_?__&_$_#_%_^_'_`. "#EC NOTEXT
CLASS-DATA so_type_s TYPE REF TO cl_abap_elemdescr .
CLASS-DATA so_type_f TYPE REF TO cl_abap_elemdescr .
CLASS-DATA so_type_p TYPE REF TO cl_abap_elemdescr .
CLASS-DATA so_type_i TYPE REF TO cl_abap_elemdescr .
CLASS-DATA so_type_b TYPE REF TO cl_abap_elemdescr .
CLASS-DATA so_type_t_json TYPE REF TO cl_abap_tabledescr .
CLASS-DATA so_type_t_name_value TYPE REF TO cl_abap_tabledescr .
CLASS-METHODS unescape
IMPORTING
escaped TYPE string
RETURNING
VALUE(unescaped) TYPE string .
CLASS-METHODS get_convexit_func
IMPORTING
elem_descr TYPE REF TO cl_abap_elemdescr
input TYPE abap_bool OPTIONAL
RETURNING
VALUE(rv_func) TYPE string .
METHODS dump_symbols
FINAL
IMPORTING
it_symbols TYPE t_t_symbol
opt_array TYPE bool OPTIONAL
format_scope TYPE bool DEFAULT abap_true
level TYPE i
RETURNING
VALUE(r_json) TYPE json .
METHODS get_symbols_struct
FINAL
IMPORTING
type_descr TYPE REF TO cl_abap_structdescr
include_aliases TYPE abap_bool DEFAULT abap_false
data TYPE REF TO data OPTIONAL
level TYPE i DEFAULT 0
RETURNING
VALUE(result) TYPE t_s_struct_cache_res .
METHODS get_symbols_class
FINAL
IMPORTING
type_descr TYPE REF TO cl_abap_classdescr
object TYPE REF TO object OPTIONAL
RETURNING
VALUE(result) TYPE t_t_symbol .
METHODS get_symbols
FINAL
IMPORTING
type_descr TYPE REF TO cl_abap_typedescr
data TYPE REF TO data OPTIONAL
object TYPE REF TO object OPTIONAL
include_aliases TYPE abap_bool DEFAULT abap_false
RETURNING
VALUE(result) TYPE t_t_symbol .
METHODS get_fields
FINAL
IMPORTING
type_descr TYPE REF TO cl_abap_typedescr
data TYPE REF TO data OPTIONAL
object TYPE REF TO object OPTIONAL
RETURNING
VALUE(rt_fields) TYPE t_t_field_cache .
METHODS dump_int
IMPORTING
data TYPE data
type_descr TYPE REF TO cl_abap_typedescr OPTIONAL
convexit TYPE string OPTIONAL
level TYPE i DEFAULT 0
RETURNING
VALUE(r_json) TYPE json .
METHODS is_compressable
IMPORTING
type_descr TYPE REF TO cl_abap_typedescr
name TYPE csequence
RETURNING
VALUE(rv_compress) TYPE abap_bool .
METHODS restore
IMPORTING
json TYPE json
length TYPE i
VALUE(type_descr) TYPE REF TO cl_abap_typedescr OPTIONAL
field_cache TYPE t_t_field_cache OPTIONAL
CHANGING
data TYPE data OPTIONAL
offset TYPE i DEFAULT 0
RAISING
cx_sy_move_cast_error .
METHODS restore_type
IMPORTING
json TYPE json
length TYPE i
VALUE(type_descr) TYPE REF TO cl_abap_typedescr OPTIONAL
field_cache TYPE t_t_field_cache OPTIONAL
convexit TYPE string OPTIONAL
CHANGING
data TYPE data OPTIONAL
offset TYPE i DEFAULT 0
RAISING
cx_sy_move_cast_error .
METHODS dump_type
IMPORTING
data TYPE data
type_descr TYPE REF TO cl_abap_elemdescr
convexit TYPE string
RETURNING
VALUE(r_json) TYPE json .
METHODS dump_type_ex
IMPORTING
data TYPE data
RETURNING
VALUE(r_json) TYPE json .
METHODS pretty_name_ex
IMPORTING
in TYPE csequence
RETURNING
VALUE(out) TYPE string .
METHODS generate_int_ex
FINAL
IMPORTING
json TYPE json
length TYPE i
CHANGING
data TYPE data
offset TYPE i .
METHODS pretty_name
IMPORTING
in TYPE csequence
RETURNING
VALUE(out) TYPE string .
CLASS-METHODS escape
IMPORTING
in TYPE any
RETURNING
VALUE(out) TYPE string .
CLASS-METHODS edm_datetime_to_ts
IMPORTING
ticks TYPE string
offset TYPE string OPTIONAL
typekind TYPE abap_typekind
RETURNING
VALUE(r_data) TYPE string .
CLASS-METHODS get_indent
IMPORTING
level TYPE i DEFAULT 0
RETURNING
VALUE(indent) TYPE string .
METHODS generate_struct
CHANGING
fields TYPE t_t_name_value
data TYPE REF TO data .
PRIVATE SECTION.
DATA mv_extended TYPE bool .
CLASS-DATA mc_me_type TYPE string .
CLASS-DATA mc_cov_error TYPE c .
ENDCLASS.
DEFINE escape_json.
&2 = &1.
* replace all occurrences of regex `[\\"]` in &1 with `\\$0`. <-- this is slower than 2 plain replaces
REPLACE ALL OCCURRENCES OF `\` IN &2 WITH `\\`.
REPLACE ALL OCCURRENCES OF `"` IN &2 WITH `\"`.
REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>cr_lf IN &2 WITH `\r\n`.
REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>newline IN &2 WITH `\n`.
REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>horizontal_tab IN &2 WITH `\t`.
END-OF-DEFINITION.
DEFINE is_compressable.
IF mv_compress EQ abap_false.
&3 = abap_false.
ELSEIF mv_extended IS INITIAL.
&3 = abap_true.
ELSE.
&3 = is_compressable( type_descr = &1 name = &2 ).
ENDIF.
END-OF-DEFINITION.
DEFINE dump_type.
IF mv_extended IS INITIAL.
dump_type_int &1 &2 &3 &4.
ELSE.
&3 = dump_type( data = &1 type_descr = &2 convexit = &4 ).
ENDIF.
END-OF-DEFINITION.
DEFINE xstring_to_string_int.
IF mv_hex_as_base64 IS INITIAL.
MOVE &1 TO &2.
ELSE.
&2 = xstring_to_string( &1 ).
ENDIF.
END-OF-DEFINITION.
DEFINE string_to_xstring_int.
IF mv_hex_as_base64 IS INITIAL.
MOVE &1 TO &2.
ELSE.
string_to_xstring( EXPORTING in = &1 CHANGING out = &2 ).
ENDIF.
END-OF-DEFINITION.
DEFINE format_list_output.
IF mv_format_output EQ abap_true AND &2 IS NOT INITIAL.
CONCATENATE `,` lv_indent INTO lv_lb.
CONCATENATE LINES OF &2 INTO &4 SEPARATED BY lv_lb.
CONCATENATE &1 lv_indent &4 indent &3 INTO &4.
ELSE.
CONCATENATE LINES OF &2 INTO &4 SEPARATED BY `,`.
CONCATENATE &1 &4 &3 INTO &4.
ENDIF.
END-OF-DEFINITION. " format_list_output
DEFINE dump_type_int.
IF &4 IS NOT INITIAL AND &1 IS NOT INITIAL.
TRY.
CALL FUNCTION &4
EXPORTING
input = &1
IMPORTING
output = &3
EXCEPTIONS
OTHERS = 1.
IF sy-subrc IS INITIAL.
CONCATENATE `"` &3 `"` INTO &3.
ENDIF.
CATCH cx_root. "#EC NO_HANDLER
ENDTRY.
ELSE.
CASE &2->type_kind.
WHEN cl_abap_typedescr=>typekind_float OR cl_abap_typedescr=>typekind_int OR cl_abap_typedescr=>typekind_int1 OR
cl_abap_typedescr=>typekind_int2 OR cl_abap_typedescr=>typekind_packed OR `8`. " TYPEKIND_INT8 -> '8' only from 7.40.
IF &2->type_kind EQ cl_abap_typedescr=>typekind_packed AND mv_ts_as_iso8601 EQ c_bool-true AND &2->absolute_name CP `\TYPE=TIMESTAMP*`.
IF &1 IS INITIAL.
&3 = mv_initial_ts.
ELSE.
&3 = &1.
IF &2->absolute_name EQ `\TYPE=TIMESTAMP`.
CONCATENATE `"` &3(4) `-` &3+4(2) `-` &3+6(2) `T` &3+8(2) `:` &3+10(2) `:` &3+12(2) `.0000000Z"` INTO &3.
ELSEIF &2->absolute_name EQ `\TYPE=TIMESTAMPL`.
CONCATENATE `"` &3(4) `-` &3+4(2) `-` &3+6(2) `T` &3+8(2) `:` &3+10(2) `:` &3+12(2) `.` &3+15(7) `Z"` INTO &3.
ENDIF.
ENDIF.
ELSEIF &1 IS INITIAL.
&3 = `0`.
ELSE.
&3 = &1.
IF &1 LT 0.
IF &2->type_kind <> cl_abap_typedescr=>typekind_float. "float: sign is already at the beginning
SHIFT &3 RIGHT CIRCULAR.
ENDIF.
ELSE.
CONDENSE &3.
ENDIF.
ENDIF.
WHEN cl_abap_typedescr=>typekind_num.
IF mv_numc_as_string EQ abap_true.
IF &1 IS INITIAL.
&3 = `""`.
ELSE.
CONCATENATE `"` &1 `"` INTO &3.
ENDIF.
ELSE.
&3 = &1.
SHIFT &3 LEFT DELETING LEADING ` 0`.
IF &3 IS INITIAL.
&3 = `0`.
ENDIF.
ENDIF.
WHEN cl_abap_typedescr=>typekind_string OR cl_abap_typedescr=>typekind_csequence OR cl_abap_typedescr=>typekind_clike.
IF &1 IS INITIAL.
&3 = `""`.
ELSEIF &2->absolute_name EQ mc_json_type.
&3 = &1.
ELSE.
escape_json &1 &3.
CONCATENATE `"` &3 `"` INTO &3.
ENDIF.
WHEN cl_abap_typedescr=>typekind_xstring OR cl_abap_typedescr=>typekind_hex.
IF &1 IS INITIAL.
&3 = `""`.
ELSE.
xstring_to_string_int &1 &3.
CONCATENATE `"` &3 `"` INTO &3.
ENDIF.
WHEN cl_abap_typedescr=>typekind_char.
IF &2->output_length EQ 1 AND mv_bool_types CS &2->absolute_name.
IF &1 EQ c_bool-true.
&3 = `true`. "#EC NOTEXT
ELSEIF &1 IS INITIAL AND mv_bool_3state CS &2->absolute_name.
&3 = `null`. "#EC NOTEXT
ELSE.
&3 = `false`. "#EC NOTEXT
ENDIF.
ELSE.
escape_json &1 &3.
CONCATENATE `"` &3 `"` INTO &3.
ENDIF.
WHEN cl_abap_typedescr=>typekind_date.
IF &1 IS INITIAL.
&3 = mv_initial_date.
ELSE.
CONCATENATE `"` &1(4) `-` &1+4(2) `-` &1+6(2) `"` INTO &3.
ENDIF.
WHEN cl_abap_typedescr=>typekind_time.
IF &1 IS INITIAL.
&3 = mv_initial_time.
ELSE.
CONCATENATE `"` &1(2) `:` &1+2(2) `:` &1+4(2) `"` INTO &3.
ENDIF.
WHEN `k`. " cl_abap_typedescr=>typekind_enum
&3 = &1.
CONCATENATE `"` &3 `"` INTO &3.
WHEN OTHERS.
IF &1 IS INITIAL.
&3 = `null`. "#EC NOTEXT
ELSE.
&3 = &1.
ENDIF.
ENDCASE.
ENDIF.
END-OF-DEFINITION.
DEFINE format_name.
CASE &2.
WHEN pretty_mode-camel_case.
&3 = pretty_name( &1 ).
WHEN pretty_mode-extended.
&3 = pretty_name_ex( &1 ).
WHEN pretty_mode-user_low_case.
READ TABLE mt_name_mappings WITH TABLE KEY abap = &1 ASSIGNING <cache>. "#EC WARNOK
IF sy-subrc IS INITIAL.
&3 = <cache>-json.
ELSE.
&3 = &1.
TRANSLATE &3 TO LOWER CASE. "#EC SYNTCHAR
ENDIF.
WHEN pretty_mode-user.
READ TABLE mt_name_mappings WITH TABLE KEY abap = &1 ASSIGNING <cache>. "#EC WARNOK
IF sy-subrc IS INITIAL.
&3 = <cache>-json.
ELSE.
&3 = &1.
ENDIF.
WHEN pretty_mode-low_case.
&3 = &1.
TRANSLATE &3 TO LOWER CASE. "#EC SYNTCHAR
WHEN OTHERS.
&3 = &1.
ENDCASE.
END-OF-DEFINITION.
DEFINE restore_reference.
CREATE DATA data TYPE HANDLE &1.
ASSIGN data->* TO <data>.
restore_type( EXPORTING json = json length = length type_descr = &1 CHANGING offset = offset data = <data> ).
END-OF-DEFINITION.
DEFINE throw_error.
RAISE EXCEPTION TYPE cx_sy_move_cast_error.
END-OF-DEFINITION.
DEFINE while_offset_cs.
WHILE offset < length.
FIND FIRST OCCURRENCE OF json+offset(1) IN &1.
IF sy-subrc IS NOT INITIAL.
EXIT.
ENDIF.
offset = offset + 1.
ENDWHILE.
END-OF-DEFINITION.
DEFINE while_offset_not_cs.
WHILE offset < length.
FIND FIRST OCCURRENCE OF &2+offset(1) IN &1.
IF sy-subrc IS INITIAL.
EXIT.
ENDIF.
offset = offset + 1.
ENDWHILE.
END-OF-DEFINITION.
DEFINE eat_white.
while_offset_cs sv_white_space.
IF offset GE length.
throw_error.
ENDIF.
END-OF-DEFINITION.
DEFINE eat_name.
IF json+offset(1) EQ `"`.
mark = offset + 1.
offset = mark.
FIND FIRST OCCURRENCE OF `"` IN SECTION OFFSET offset OF json MATCH OFFSET offset.
IF sy-subrc IS NOT INITIAL.
throw_error.
ENDIF.
match = offset - mark.
&1 = json+mark(match).
offset = offset + 1.
ELSE.
throw_error.
ENDIF.
END-OF-DEFINITION.
DEFINE eat_string.
IF json+offset(1) EQ `"`.
mark = offset + 1.
offset = mark.
IF json+mark(1) EQ `"`.
CLEAR &1.
ELSE.
DO.
FIND FIRST OCCURRENCE OF `"` IN SECTION OFFSET offset OF json MATCH OFFSET pos.
IF sy-subrc IS NOT INITIAL.
throw_error.
ENDIF.
offset = pos.
pos = pos - 1.
" if escaped search further
WHILE pos GE 0 AND json+pos(1) EQ `\`.
pos = pos - 1.
ENDWHILE.
match = ( offset - pos ) MOD 2.
IF match NE 0.
EXIT.
ENDIF.
offset = offset + 1.
ENDDO.
match = offset - mark.
&1 = json+mark(match).
" unescaped singe characters, e.g \\, \", \/ etc,
" BUT ONLY if someone really need the data
IF type_descr IS NOT INITIAL.
&1 = unescape( &1 ).
ENDIF.
ENDIF.
offset = offset + 1.
ELSE.
throw_error.
ENDIF.
END-OF-DEFINITION.
DEFINE eat_number.
mark = offset.
while_offset_cs `0123456789+-eE.`. "#EC NOTEXT
match = offset - mark.
&1 = json+mark(match).
END-OF-DEFINITION.
DEFINE eat_bool.
mark = offset.
while_offset_cs `aeflnrstu`. "#EC NOTEXT
match = offset - mark.
IF json+mark(match) EQ `true`. "#EC NOTEXT
&1 = c_bool-true.
ELSEIF json+mark(match) EQ `false`. "#EC NOTEXT
IF type_descr IS BOUND AND mv_bool_3state CS type_descr->absolute_name.
&1 = c_tribool-false.
ELSE.
&1 = c_bool-false.
ENDIF.
ELSEIF json+mark(match) EQ `null`. "#EC NOTEXT
CLEAR &1.
ENDIF.
END-OF-DEFINITION.
DEFINE eat_char.
IF offset < length AND json+offset(1) EQ &1.
offset = offset + 1.
ELSE.
throw_error.
ENDIF.
END-OF-DEFINITION.
CLASS zcl_json IMPLEMENTATION.
METHOD bool_to_tribool.
IF iv_bool EQ c_bool-true.
rv_tribool = c_tribool-true.
ELSEIF iv_bool EQ abap_undefined. " fall back for abap _bool
rv_tribool = c_tribool-undefined.
ELSE.
rv_tribool = c_tribool-false.
ENDIF.
ENDMETHOD. "bool_to_tribool
METHOD class_constructor.
DATA: lo_bool_type_descr TYPE REF TO cl_abap_typedescr,
lo_tribool_type_descr TYPE REF TO cl_abap_typedescr,
lo_json_type_descr TYPE REF TO cl_abap_typedescr,
lv_pos LIKE sy-fdpos,
lv_json_string TYPE json.
lo_bool_type_descr = cl_abap_typedescr=>describe_by_data( c_bool-true ).
lo_tribool_type_descr = cl_abap_typedescr=>describe_by_data( c_tribool-true ).
lo_json_type_descr = cl_abap_typedescr=>describe_by_data( lv_json_string ).
CONCATENATE mc_bool_types lo_bool_type_descr->absolute_name lo_tribool_type_descr->absolute_name INTO mc_bool_types.
CONCATENATE mc_bool_3state lo_tribool_type_descr->absolute_name INTO mc_bool_3state.
CONCATENATE mc_json_type lo_json_type_descr->absolute_name INTO mc_json_type.
FIND FIRST OCCURRENCE OF `\TYPE=` IN lo_json_type_descr->absolute_name MATCH OFFSET lv_pos.
IF sy-subrc IS INITIAL.
mc_me_type = lo_json_type_descr->absolute_name(lv_pos).
ENDIF.
sv_white_space = cl_abap_char_utilities=>get_simple_spaces_for_cur_cp( ).
mc_cov_error = cl_abap_conv_in_ce=>uccp( '0000' ).
so_type_s = cl_abap_elemdescr=>get_string( ).
so_type_f = cl_abap_elemdescr=>get_f( ).
so_type_p ?= cl_abap_typedescr=>describe_by_name( 'p' ).
so_type_i = cl_abap_elemdescr=>get_i( ).
so_type_b ?= cl_abap_typedescr=>describe_by_name( 'ABAP_BOOL' ).
so_type_t_json ?= cl_abap_typedescr=>describe_by_name( 'T_T_JSON' ).
so_type_t_name_value ?= cl_abap_typedescr=>describe_by_name( 'T_T_NAME_VALUE' ).
ENDMETHOD. "class_constructor
METHOD constructor.
DATA: rtti TYPE REF TO cl_abap_classdescr,
pair LIKE LINE OF name_mappings.
mv_compress = compress.
mv_pretty_name = pretty_name.
mv_assoc_arrays = assoc_arrays.
mv_ts_as_iso8601 = ts_as_iso8601.
mv_expand_includes = expand_includes.
mv_assoc_arrays_opt = assoc_arrays_opt.
mv_strict_mode = strict_mode.
mv_numc_as_string = numc_as_string.
mv_conversion_exits = conversion_exits.
mv_format_output = format_output.
mv_hex_as_base64 = hex_as_base64.
mv_bool_types = bool_types.
mv_bool_3state = bool_3state.
mv_initial_ts = initial_ts.
mv_initial_date = initial_date.
mv_initial_time = initial_time.
LOOP AT name_mappings INTO pair.
TRANSLATE pair-abap TO UPPER CASE.
INSERT pair INTO TABLE mt_name_mappings.
ENDLOOP.
" if it dumps here, you have passed ambiguous mapping to the API
" please check your code for duplicates, pairs ABAP - JSON shall be unique
INSERT LINES OF mt_name_mappings INTO TABLE mt_name_mappings_ex.
IF mt_name_mappings IS NOT INITIAL.
IF mv_pretty_name EQ pretty_mode-none.
mv_pretty_name = pretty_mode-user.
ELSEIF pretty_name EQ pretty_mode-low_case.
mv_pretty_name = pretty_mode-user_low_case.
ENDIF.
ENDIF.
rtti ?= cl_abap_classdescr=>describe_by_object_ref( me ).
IF rtti->absolute_name NE mc_me_type.
mv_extended = c_bool-true.
ENDIF.
ENDMETHOD.
METHOD deserialize.
DATA: lo_json TYPE REF TO zcl_json.
IF json IS NOT INITIAL OR jsonx IS NOT INITIAL.
CREATE OBJECT lo_json
EXPORTING
pretty_name = pretty_name
name_mappings = name_mappings
assoc_arrays = assoc_arrays
conversion_exits = conversion_exits
hex_as_base64 = hex_as_base64
assoc_arrays_opt = assoc_arrays_opt.
TRY .
lo_json->deserialize_int( EXPORTING json = json jsonx = jsonx CHANGING data = data ).
CATCH cx_sy_move_cast_error. "
ENDTRY.
ENDIF.
ENDMETHOD. "deserialize
METHOD deserialize_int.
DATA: length TYPE i,
offset TYPE i,
unescaped LIKE json.
IF json IS NOT INITIAL OR jsonx IS NOT INITIAL.
IF jsonx IS NOT INITIAL.
unescaped = raw_to_string( jsonx ).
ELSE.
unescaped = json.
ENDIF.
" skip leading BOM signs
length = strlen( unescaped ).
while_offset_not_cs `"{[` unescaped.
restore_type( EXPORTING json = unescaped length = length CHANGING data = data offset = offset ).
ENDIF.
ENDMETHOD. "deserialize
METHOD dump.
DATA: lo_json TYPE REF TO zcl_json.
CREATE OBJECT lo_json
EXPORTING
compress = compress
pretty_name = pretty_name
assoc_arrays = assoc_arrays
ts_as_iso8601 = ts_as_iso8601.
r_json = lo_json->dump_int( data = data type_descr = type_descr ).
ENDMETHOD. "dump
METHOD dump_int.
DATA: lo_typedesc TYPE REF TO cl_abap_typedescr,
lo_elem_descr TYPE REF TO cl_abap_elemdescr,
lo_classdesc TYPE REF TO cl_abap_classdescr,
lo_structdesc TYPE REF TO cl_abap_structdescr,
lo_tabledescr TYPE REF TO cl_abap_tabledescr,
ls_struct_sym TYPE t_s_struct_cache_res,
lt_symbols TYPE t_t_symbol,
lt_keys TYPE STANDARD TABLE OF REF TO data WITH DEFAULT KEY,
lt_properties TYPE STANDARD TABLE OF string,
lo_obj_ref TYPE REF TO object,
lo_data_ref TYPE REF TO data,
ls_skip_key TYPE LINE OF abap_keydescr_tab,
lv_array_opt TYPE abap_bool,
indent TYPE string,
lv_indent LIKE indent,
lv_level LIKE level,
lv_lb TYPE string,
lv_prop_name TYPE string,
lv_keyval TYPE string,
lv_itemval TYPE string.
FIELD-SYMBOLS: <line> TYPE any,
<value> TYPE any,
<data> TYPE data,
<key> TYPE LINE OF abap_keydescr_tab,
<symbol> TYPE t_s_symbol,
<table> TYPE ANY TABLE.
" increase hierarchy level
lv_level = level + 1.
" we need here macro instead of method calls because of the performance reasons.
" based on SAT measurements.
CASE type_descr->kind.
WHEN cl_abap_typedescr=>kind_ref.
IF data IS INITIAL.
r_json = `null`. "#EC NOTEXT
ELSEIF type_descr->type_kind EQ cl_abap_typedescr=>typekind_dref.
lo_data_ref ?= data.
lo_typedesc = cl_abap_typedescr=>describe_by_data_ref( lo_data_ref ).
ASSIGN lo_data_ref->* TO <data>.
r_json = dump_int( data = <data> type_descr = lo_typedesc level = level ).
ELSE.
lo_obj_ref ?= data.
lo_classdesc ?= cl_abap_typedescr=>describe_by_object_ref( lo_obj_ref ).
lt_symbols = get_symbols_class( type_descr = lo_classdesc object = lo_obj_ref ).
r_json = dump_symbols( it_symbols = lt_symbols level = level ).
ENDIF.
WHEN cl_abap_typedescr=>kind_elem.
lo_elem_descr ?= type_descr.
dump_type data lo_elem_descr r_json convexit.
WHEN cl_abap_typedescr=>kind_struct.
lo_structdesc ?= type_descr.
ls_struct_sym = get_symbols_struct( type_descr = lo_structdesc level = level ).
ASSIGN ls_struct_sym-data->* TO <data>.
<data> = data.
r_json = dump_symbols( it_symbols = ls_struct_sym-symbols level = level ).
WHEN cl_abap_typedescr=>kind_table.
lo_tabledescr ?= type_descr.
lo_typedesc = lo_tabledescr->get_table_line_type( ).
ASSIGN data TO <table>.
IF mv_format_output EQ abap_true.
indent = get_indent( level ).
lv_indent = get_indent( lv_level ).
ENDIF.
" optimization for structured tables
IF lo_typedesc->kind EQ cl_abap_typedescr=>kind_struct.
lo_structdesc ?= lo_typedesc.
ls_struct_sym = get_symbols_struct( type_descr = lo_structdesc level = level ).
ASSIGN ls_struct_sym-data->* TO <line>.
" here we have differentiation of output of simple table to JSON array
" and sorted or hashed table with unique key into JSON associative array
IF lo_tabledescr->has_unique_key IS NOT INITIAL AND mv_assoc_arrays IS NOT INITIAL.
IF lo_tabledescr->key_defkind EQ lo_tabledescr->keydefkind_user.
LOOP AT lo_tabledescr->key ASSIGNING <key>.
READ TABLE ls_struct_sym-symbols WITH KEY name = <key>-name ASSIGNING <symbol>.
APPEND <symbol>-value TO lt_keys.
ENDLOOP.
ENDIF.
IF lines( lo_tabledescr->key ) EQ 1.
READ TABLE lo_tabledescr->key INDEX 1 INTO ls_skip_key.
DELETE ls_struct_sym-symbols WHERE name EQ ls_skip_key-name.
" remove object wrapping for simple name-value tables
IF mv_assoc_arrays_opt EQ abap_true AND lines( ls_struct_sym-symbols ) EQ 1.
lv_array_opt = abap_true.
ENDIF.
ENDIF.
LOOP AT <table> INTO <line>.
CLEAR: lv_prop_name.
" construct key attribute name
IF lo_tabledescr->key_defkind EQ lo_tabledescr->keydefkind_user.
LOOP AT lt_keys INTO lo_data_ref.
ASSIGN lo_data_ref->* TO <value>.
lv_keyval = <value>.
CONDENSE lv_keyval.
IF lv_prop_name IS NOT INITIAL.
CONCATENATE lv_prop_name mc_key_separator lv_keyval INTO lv_prop_name.
ELSE.
lv_prop_name = lv_keyval.
ENDIF.
ENDLOOP.
ELSE.
LOOP AT ls_struct_sym-symbols ASSIGNING <symbol>.
ASSIGN <symbol>-value->* TO <value>.
lv_keyval = <value>.
CONDENSE lv_keyval.
IF lv_prop_name IS NOT INITIAL.
CONCATENATE lv_prop_name mc_key_separator lv_keyval INTO lv_prop_name.
ELSE.
lv_prop_name = lv_keyval.
ENDIF.
ENDLOOP.
ENDIF.
lv_itemval = dump_symbols( it_symbols = ls_struct_sym-symbols opt_array = lv_array_opt format_scope = abap_false level = lv_level ).
IF lv_array_opt EQ abap_true.
IF mv_format_output EQ abap_true AND lv_itemval IS NOT INITIAL.
CONCATENATE `"` lv_prop_name `": ` lv_itemval INTO lv_itemval.
ELSE.
CONCATENATE `"` lv_prop_name `":` lv_itemval INTO lv_itemval.
ENDIF.
ELSE.
IF mv_format_output EQ abap_true AND lv_itemval IS NOT INITIAL.
CONCATENATE `"` lv_prop_name `": {` lv_itemval lv_indent `}` INTO lv_itemval.
ELSE.
CONCATENATE `"` lv_prop_name `":{` lv_itemval `}` INTO lv_itemval.
ENDIF.
ENDIF.
APPEND lv_itemval TO lt_properties.
ENDLOOP.
format_list_output `{` lt_properties `}` r_json.
ELSE.
LOOP AT <table> INTO <line>.
lv_itemval = dump_symbols( it_symbols = ls_struct_sym-symbols level = lv_level ).
APPEND lv_itemval TO lt_properties.
ENDLOOP.
format_list_output `[` lt_properties `]` r_json.
ENDIF.
ELSE.
LOOP AT <table> ASSIGNING <value>.
lv_itemval = dump_int( data = <value> type_descr = lo_typedesc level = lv_level ).
APPEND lv_itemval TO lt_properties.
ENDLOOP.
format_list_output `[` lt_properties `]` r_json.
ENDIF.
ENDCASE.
ENDMETHOD. "dump
METHOD dump_symbols.
DATA: lt_fields TYPE STANDARD TABLE OF string,
lv_indent TYPE string,
lv_level LIKE level,
lv_itemval TYPE string.
FIELD-SYMBOLS: <value> TYPE any,
<symbol> LIKE LINE OF it_symbols.
" increase hierarchy level
lv_level = level + 1.
IF mv_format_output EQ abap_true AND opt_array EQ abap_false.
lv_indent = get_indent( lv_level ).
ENDIF.
LOOP AT it_symbols ASSIGNING <symbol>.
ASSIGN <symbol>-value->* TO <value>.
CHECK <symbol>-compressable EQ abap_false OR <value> IS NOT INITIAL OR opt_array EQ abap_true.
IF <symbol>-elem_type IS NOT INITIAL.
dump_type <value> <symbol>-elem_type lv_itemval <symbol>-convexit_out.
ELSE.
lv_itemval = dump_int( data = <value> type_descr = <symbol>-type convexit = <symbol>-convexit_out level = lv_level ).
ENDIF.
IF opt_array EQ abap_false.
IF mv_format_output EQ abap_true.
CONCATENATE lv_indent <symbol>-header lv_itemval INTO lv_itemval.
ELSE.
CONCATENATE <symbol>-header lv_itemval INTO lv_itemval.
ENDIF.
ENDIF.
APPEND lv_itemval TO lt_fields.
ENDLOOP.
CONCATENATE LINES OF lt_fields INTO r_json SEPARATED BY `,`.
IF format_scope EQ abap_true.
IF r_json IS INITIAL.
r_json = `{}`.
ELSEIF mv_format_output EQ abap_true.
lv_indent = get_indent( level ).
CONCATENATE `{` r_json lv_indent `}` INTO r_json.
ELSE.
CONCATENATE `{` r_json `}` INTO r_json.
ENDIF.
ENDIF.
ENDMETHOD.
METHOD dump_type.
CONSTANTS: lc_typekind_utclong TYPE abap_typekind VALUE 'p', " CL_ABAP_TYPEDESCR=>TYPEKIND_UTCLONG -> 'p' only from 7.60
lc_typekind_int8 TYPE abap_typekind VALUE '8'. " TYPEKIND_INT8 -> '8' only from 7.40
IF convexit IS NOT INITIAL AND data IS NOT INITIAL.
TRY.
CALL FUNCTION convexit
EXPORTING
input = data
IMPORTING
output = r_json
EXCEPTIONS
OTHERS = 1.
IF sy-subrc IS INITIAL.
CONCATENATE `"` r_json `"` INTO r_json.
ENDIF.
CATCH cx_root. "#EC NO_HANDLER
ENDTRY.
ELSE.
CASE type_descr->type_kind.
WHEN cl_abap_typedescr=>typekind_float OR cl_abap_typedescr=>typekind_int OR cl_abap_typedescr=>typekind_int1 OR
cl_abap_typedescr=>typekind_int2 OR cl_abap_typedescr=>typekind_packed OR lc_typekind_utclong OR lc_typekind_int8.
IF mv_ts_as_iso8601 EQ c_bool-true AND
( type_descr->type_kind EQ lc_typekind_utclong OR
( type_descr->type_kind EQ cl_abap_typedescr=>typekind_packed AND type_descr->absolute_name CP `\TYPE=TIMESTAMP*` ) ).
IF data IS INITIAL.
r_json = mv_initial_ts.
ELSE.
r_json = data.
IF type_descr->absolute_name EQ `\TYPE=TIMESTAMP`.
CONCATENATE `"` r_json(4) `-` r_json+4(2) `-` r_json+6(2) `T` r_json+8(2) `:` r_json+10(2) `:` r_json+12(2) `.0000000Z"` INTO r_json.
ELSEIF type_descr->absolute_name EQ `\TYPE=TIMESTAMPL`.
CONCATENATE `"` r_json(4) `-` r_json+4(2) `-` r_json+6(2) `T` r_json+8(2) `:` r_json+10(2) `:` r_json+12(2) `.` r_json+15(7) `Z"` INTO r_json.
ENDIF.
ENDIF.
ELSEIF data IS INITIAL.
r_json = `0`.
ELSE.
r_json = data.
IF data LT 0.
IF type_descr->type_kind <> cl_abap_typedescr=>typekind_float. "float: sign is already at the beginning
SHIFT r_json RIGHT CIRCULAR.
ENDIF.
ELSE.
CONDENSE r_json.
ENDIF.
ENDIF.
WHEN cl_abap_typedescr=>typekind_num.
IF mv_numc_as_string EQ abap_true.
IF data IS INITIAL.
r_json = `""`.
ELSE.
CONCATENATE `"` data `"` INTO r_json.
ENDIF.
ELSE.
r_json = data.
SHIFT r_json LEFT DELETING LEADING ` 0`.
IF r_json IS INITIAL.
r_json = `0`.
ENDIF.
ENDIF.
WHEN cl_abap_typedescr=>typekind_string OR cl_abap_typedescr=>typekind_csequence OR cl_abap_typedescr=>typekind_clike.
IF data IS INITIAL.
r_json = `""`.
ELSEIF type_descr->absolute_name EQ mc_json_type.
r_json = data.
ELSE.
r_json = escape( data ).
CONCATENATE `"` r_json `"` INTO r_json.
ENDIF.
WHEN cl_abap_typedescr=>typekind_xstring OR cl_abap_typedescr=>typekind_hex.
IF data IS INITIAL.
r_json = `""`.
ELSE.
xstring_to_string_int data r_json.
CONCATENATE `"` r_json `"` INTO r_json.
ENDIF.
WHEN cl_abap_typedescr=>typekind_char.
IF type_descr->output_length EQ 1 AND mv_bool_types CS type_descr->absolute_name.
IF data EQ c_bool-true.
r_json = `true`. "#EC NOTEXT
ELSEIF data IS INITIAL AND mv_bool_3state CS type_descr->absolute_name.
r_json = `null`. "#EC NOTEXT
ELSE.
r_json = `false`. "#EC NOTEXT
ENDIF.
ELSE.
r_json = escape( data ).
CONCATENATE `"` r_json `"` INTO r_json.
ENDIF.
WHEN cl_abap_typedescr=>typekind_date.
IF data IS INITIAL.
r_json = mv_initial_date.
ELSE.
CONCATENATE `"` data(4) `-` data+4(2) `-` data+6(2) `"` INTO r_json.
ENDIF.
WHEN cl_abap_typedescr=>typekind_time.
IF data IS INITIAL.
r_json = mv_initial_time.
ELSE.
CONCATENATE `"` data(2) `:` data+2(2) `:` data+4(2) `"` INTO r_json.
ENDIF.
WHEN 'k'. " cl_abap_typedescr=>typekind_enum
r_json = data.
CONCATENATE `"` r_json `"` INTO r_json.
WHEN OTHERS.
IF data IS INITIAL.
r_json = `null`. "#EC NOTEXT
ELSE.
r_json = data.
ENDIF.
ENDCASE.
ENDIF.
ENDMETHOD. "dump_type
METHOD dump_type_ex.
DATA: lo_descr TYPE REF TO cl_abap_elemdescr,
lv_convexit TYPE string.
lo_descr ?= cl_abap_typedescr=>describe_by_data( data ).
IF mv_conversion_exits EQ abap_true.
lv_convexit = get_convexit_func( elem_descr = lo_descr input = abap_false ).
ENDIF.
r_json = dump_type( data = data type_descr = lo_descr convexit = lv_convexit ).
ENDMETHOD. "DUMP_TYPE_EX
METHOD edm_datetime_to_ts.
CONSTANTS: lc_epochs TYPE string VALUE `19700101000000`.
DATA: lv_ticks TYPE p,
lv_seconds TYPE p,
lv_subsec TYPE p,
lv_timestamps TYPE string,
lv_timestamp TYPE timestampl VALUE `19700101000000.0000000`.
lv_ticks = ticks.
lv_seconds = lv_ticks / 1000. " in seconds
lv_subsec = lv_ticks MOD 1000. " in subsec
IF lv_subsec GT 0.
lv_timestamps = lv_subsec.
CONCATENATE lc_epochs `.` lv_timestamps INTO lv_timestamps.
lv_timestamp = lv_timestamps.
ENDIF.
lv_timestamp = cl_abap_tstmp=>add( tstmp = lv_timestamp secs = lv_seconds ).
IF offset IS NOT INITIAL.
lv_ticks = offset+1.
lv_ticks = lv_ticks * 60. "offset is in minutes
IF offset(1) = '+'.
lv_timestamp = cl_abap_tstmp=>subtractsecs( tstmp = lv_timestamp secs = lv_ticks ).
ELSE.
lv_timestamp = cl_abap_tstmp=>add( tstmp = lv_timestamp secs = lv_ticks ).
ENDIF.
ENDIF.
CASE typekind.
WHEN cl_abap_typedescr=>typekind_time.
r_data = lv_timestamp.
r_data = r_data+8(6).
WHEN cl_abap_typedescr=>typekind_date.
r_data = lv_timestamp.
r_data = r_data(8).
WHEN cl_abap_typedescr=>typekind_packed.
r_data = lv_timestamp.
ENDCASE.
ENDMETHOD.
METHOD escape.
escape_json in out.
ENDMETHOD. "escape
METHOD generate.
DATA: lo_json TYPE REF TO zcl_json,
offset TYPE i,
length TYPE i.
" skip leading BOM signs
length = strlen( json ).
while_offset_not_cs `"{[` json.
CREATE OBJECT lo_json
EXPORTING
pretty_name = pretty_name
name_mappings = name_mappings
assoc_arrays = c_bool-true
assoc_arrays_opt = c_bool-true.
TRY .
lo_json->generate_int( EXPORTING json = json length = length CHANGING offset = offset data = rr_data ).
CATCH cx_sy_move_cast_error. "#EC NO_HANDLER
ENDTRY.
ENDMETHOD.
METHOD generate_int.
DATA: lt_json TYPE t_t_json,
lt_fields TYPE t_t_name_value.
FIELD-SYMBOLS: <data> TYPE data,
<struct> TYPE data,
<json> LIKE LINE OF lt_json,
<field> LIKE LINE OF lt_fields,
<table> TYPE STANDARD TABLE.
IF length IS NOT SUPPLIED.
length = strlen( json ).
ENDIF.
eat_white.
CASE json+offset(1).
WHEN `{`."result must be a structure
restore_type( EXPORTING json = json length = length type_descr = so_type_t_name_value CHANGING offset = offset data = lt_fields ).
generate_struct( CHANGING fields = lt_fields data = data ).
IF data IS BOUND.
ASSIGN data->* TO <struct>.
LOOP AT lt_fields ASSIGNING <field>.
ASSIGN COMPONENT sy-tabix OF STRUCTURE <struct> TO <data>.
generate_int( EXPORTING json = <field>-value CHANGING data = <data> ).
ENDLOOP.
ENDIF.
WHEN `[`."result must be a table of ref
restore_type( EXPORTING json = json length = length type_descr = so_type_t_json CHANGING offset = offset data = lt_json ).
CREATE DATA data TYPE ref_tab.
ASSIGN data->* TO <table>.
LOOP AT lt_json ASSIGNING <json>.
APPEND INITIAL LINE TO <table> ASSIGNING <data>.
generate_int( EXPORTING json = <json> CHANGING data = <data> ).
ENDLOOP.
WHEN `"`."string
restore_reference so_type_s.
WHEN `-` OR `0` OR `1` OR `2` OR `3` OR `4` OR `5` OR `6` OR `7` OR `8` OR `9`. " number
IF json+offset CS '.'.
restore_reference so_type_f.
ELSEIF length GT 9.
restore_reference so_type_p.
ELSE.
restore_reference so_type_i.
ENDIF.
WHEN OTHERS.
IF json+offset EQ `true` OR json+offset EQ `false`. "#EC NOTEXT
restore_reference so_type_b.
ENDIF.
ENDCASE.
ENDMETHOD.
METHOD generate_int_ex.
DATA: lv_assoc_arrays LIKE mv_assoc_arrays,
lv_assoc_arrays_opt LIKE mv_assoc_arrays_opt.
lv_assoc_arrays = mv_assoc_arrays.
lv_assoc_arrays_opt = mv_assoc_arrays_opt.
mv_assoc_arrays = abap_true.
mv_assoc_arrays_opt = abap_true.
generate_int( EXPORTING json = json length = length CHANGING offset = offset data = data ).
mv_assoc_arrays = lv_assoc_arrays.
mv_assoc_arrays_opt = lv_assoc_arrays_opt.
ENDMETHOD.
METHOD get_convexit_func.
DATA: ls_dfies TYPE dfies.
elem_descr->get_ddic_field(
RECEIVING
p_flddescr = ls_dfies " Field Description
EXCEPTIONS
not_found = 1
no_ddic_type = 2
OTHERS = 3
).
IF sy-subrc IS INITIAL AND ls_dfies-convexit IS NOT INITIAL.
IF input EQ abap_true.
CONCATENATE 'CONVERSION_EXIT_' ls_dfies-convexit '_INPUT' INTO rv_func.
ELSE.
CONCATENATE 'CONVERSION_EXIT_' ls_dfies-convexit '_OUTPUT' INTO rv_func.
ENDIF.
ENDIF.
ENDMETHOD.
METHOD get_fields.
DATA: lt_symbols TYPE t_t_symbol,
lv_name TYPE char128,
ls_field LIKE LINE OF rt_fields.
FIELD-SYMBOLS: <sym> LIKE LINE OF lt_symbols,
<cache> LIKE LINE OF mt_name_mappings.
lt_symbols = get_symbols( type_descr = type_descr data = data object = object include_aliases = abap_true ).
LOOP AT lt_symbols ASSIGNING <sym> WHERE read_only EQ abap_false.
MOVE-CORRESPONDING <sym> TO ls_field.
" insert as UPPER CASE
INSERT ls_field INTO TABLE rt_fields.
" insert as lower case
TRANSLATE ls_field-name TO LOWER CASE.
INSERT ls_field INTO TABLE rt_fields.
" as pretty printed
IF mv_pretty_name NE pretty_mode-none AND mv_pretty_name NE pretty_mode-low_case.
format_name <sym>-name mv_pretty_name ls_field-name.
INSERT ls_field INTO TABLE rt_fields.
" let us check for not well formed canelCase to be compatible with old logic
lv_name = ls_field-name.
TRANSLATE lv_name(1) TO UPPER CASE.
ls_field-name = lv_name.
INSERT ls_field INTO TABLE rt_fields.
ENDIF.
ENDLOOP.
ENDMETHOD.
METHOD get_symbols.
DATA: class_descr TYPE REF TO cl_abap_classdescr,
struct_descr TYPE REF TO cl_abap_structdescr,
struct_cache TYPE t_s_struct_cache_res.
IF type_descr->kind EQ cl_abap_typedescr=>kind_struct.
struct_descr ?= type_descr.
struct_cache = get_symbols_struct( type_descr = struct_descr data = data include_aliases = include_aliases ).
result = struct_cache-symbols.
ELSEIF type_descr->type_kind EQ cl_abap_typedescr=>typekind_class.
class_descr ?= type_descr.
result = get_symbols_class( type_descr = class_descr object = object ).
ENDIF.
ENDMETHOD. "GET_SYMBOLS
METHOD is_compressable.
rv_compress = abap_true.
ENDMETHOD.
METHOD pretty_name.
DATA: tokens TYPE TABLE OF char128,
cache LIKE LINE OF mt_name_mappings.
FIELD-SYMBOLS: <token> LIKE LINE OF tokens,
<cache> LIKE LINE OF mt_name_mappings.
READ TABLE mt_name_mappings WITH TABLE KEY abap = in ASSIGNING <cache>.
IF sy-subrc IS INITIAL.
out = <cache>-json.
ELSE.
out = in.
REPLACE ALL OCCURRENCES OF `__` IN out WITH `*`.
TRANSLATE out TO LOWER CASE.
TRANSLATE out USING `/_:_~_`.
SPLIT out AT `_` INTO TABLE tokens.
LOOP AT tokens ASSIGNING <token> FROM 2.
TRANSLATE <token>(1) TO UPPER CASE.
ENDLOOP.
CONCATENATE LINES OF tokens INTO out.
REPLACE ALL OCCURRENCES OF `*` IN out WITH `_`.
cache-abap = in.
cache-json = out.
INSERT cache INTO TABLE mt_name_mappings.
INSERT cache INTO TABLE mt_name_mappings_ex.
ENDIF.
ENDMETHOD. "pretty_name
METHOD pretty_name_ex.
DATA: tokens TYPE TABLE OF char128,
cache LIKE LINE OF mt_name_mappings.
FIELD-SYMBOLS: <token> LIKE LINE OF tokens,
<cache> LIKE LINE OF mt_name_mappings.
READ TABLE mt_name_mappings WITH TABLE KEY abap = in ASSIGNING <cache>.
IF sy-subrc IS INITIAL.
out = <cache>-json.
ELSE.
out = in.
TRANSLATE out TO LOWER CASE.
TRANSLATE out USING `/_:_~_`.
REPLACE ALL OCCURRENCES OF `__e__` IN out WITH `!`.
REPLACE ALL OCCURRENCES OF `__n__` IN out WITH `
REPLACE ALL OCCURRENCES OF `__d__` IN out WITH `$`.
REPLACE ALL OCCURRENCES OF `__p__` IN out WITH `%`.
REPLACE ALL OCCURRENCES OF `__m__` IN out WITH `&`.
REPLACE ALL OCCURRENCES OF `__s__` IN out WITH `*`.
REPLACE ALL OCCURRENCES OF `__h__` IN out WITH `-`.
REPLACE ALL OCCURRENCES OF `__t__` IN out WITH `~`.
REPLACE ALL OCCURRENCES OF `__l__` IN out WITH `/`.
REPLACE ALL OCCURRENCES OF `__c__` IN out WITH `:`.
REPLACE ALL OCCURRENCES OF `__v__` IN out WITH `|`.
REPLACE ALL OCCURRENCES OF `__a__` IN out WITH `@`.
REPLACE ALL OCCURRENCES OF `__o__` IN out WITH `.`.
REPLACE ALL OCCURRENCES OF `___` IN out WITH `.`.
REPLACE ALL OCCURRENCES OF `__` IN out WITH `"`.
SPLIT out AT `_` INTO TABLE tokens.
LOOP AT tokens ASSIGNING <token> FROM 2.
TRANSLATE <token>(1) TO UPPER CASE.
ENDLOOP.
CONCATENATE LINES OF tokens INTO out.
REPLACE ALL OCCURRENCES OF `"` IN out WITH `_`.
cache-abap = in.
cache-json = out.
INSERT cache INTO TABLE mt_name_mappings.
INSERT cache INTO TABLE mt_name_mappings_ex.
ENDIF.
ENDMETHOD. "pretty_name_ex
METHOD raw_to_string.
DATA: lv_output_length TYPE i,
lt_binary_tab TYPE STANDARD TABLE OF sdokcntbin.
CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'
EXPORTING
buffer = iv_xstring
IMPORTING
output_length = lv_output_length
TABLES
binary_tab = lt_binary_tab.
CALL FUNCTION 'SCMS_BINARY_TO_STRING'
EXPORTING
input_length = lv_output_length
encoding = iv_encoding
IMPORTING
text_buffer = rv_string
output_length = lv_output_length
TABLES
binary_tab = lt_binary_tab.
ENDMETHOD.
METHOD restore.
DATA: mark LIKE offset,
match LIKE offset,
ref_descr TYPE REF TO cl_abap_refdescr,
data_descr TYPE REF TO cl_abap_datadescr,
data_ref TYPE REF TO data,
object_ref TYPE REF TO object,
fields LIKE field_cache,
name_json TYPE string.
FIELD-SYMBOLS: <value> TYPE any,
<field_cache> LIKE LINE OF field_cache.
fields = field_cache.
IF type_descr IS NOT INITIAL AND type_descr->kind EQ type_descr->kind_ref.
ref_descr ?= type_descr.
type_descr = ref_descr->get_referenced_type( ).
IF ref_descr->type_kind EQ ref_descr->typekind_oref.
IF data IS INITIAL.
" can fire an exception, if type is abstract or constructor protected
CREATE OBJECT data TYPE (type_descr->absolute_name).
ELSE.
type_descr = cl_abap_typedescr=>describe_by_object_ref( data ).
ENDIF.
object_ref ?= data.
fields = get_fields( type_descr = type_descr object = object_ref ).
ELSEIF ref_descr->type_kind EQ ref_descr->typekind_dref.
IF data IS INITIAL.
data_descr ?= type_descr.
CREATE DATA data TYPE HANDLE data_descr.
ELSE.
type_descr = cl_abap_typedescr=>describe_by_data_ref( data ).
ENDIF.
data_ref ?= data.
ASSIGN data_ref->* TO <value>.
fields = get_fields( type_descr = type_descr data = data_ref ).
restore( EXPORTING json = json
length = length
type_descr = type_descr
field_cache = fields
CHANGING data = <value>
offset = offset ).
RETURN.
ENDIF.
ENDIF.
IF fields IS INITIAL AND type_descr IS NOT INITIAL AND type_descr->kind EQ type_descr->kind_struct.
GET REFERENCE OF data INTO data_ref.
fields = get_fields( type_descr = type_descr data = data_ref ).
ENDIF.
eat_white.
eat_char `{`.
eat_white.
WHILE offset < length AND json+offset(1) NE `}`.
eat_name name_json.
eat_white.
eat_char `:`.
eat_white.
READ TABLE fields WITH TABLE KEY name = name_json ASSIGNING <field_cache>.
IF sy-subrc IS NOT INITIAL.
TRANSLATE name_json TO UPPER CASE.
READ TABLE fields WITH TABLE KEY name = name_json ASSIGNING <field_cache>.
ENDIF.
IF sy-subrc IS INITIAL.
ASSIGN <field_cache>-value->* TO <value>.
restore_type( EXPORTING json = json length = length type_descr = <field_cache>-type convexit = <field_cache>-convexit_in CHANGING data = <value> offset = offset ).
ELSE.
restore_type( EXPORTING json = json length = length CHANGING offset = offset ).
ENDIF.
eat_white.
IF offset < length AND json+offset(1) NE `}`.
eat_char `,`.
eat_white.
ELSE.
EXIT.
ENDIF.
ENDWHILE.
eat_char `}`.
ENDMETHOD. "restore
METHOD restore_type.
DATA: mark LIKE offset,
match LIKE offset,
sdummy TYPE string, "#EC NEEDED
rdummy TYPE REF TO data, "#EC NEEDED
pos LIKE offset,
line TYPE REF TO data,
key_ref TYPE REF TO data,
data_ref TYPE REF TO data,
key_name TYPE string,
key_value TYPE string,
lt_fields LIKE field_cache,
ls_symbols TYPE t_s_struct_cache_res,
lv_ticks TYPE string,
lv_offset TYPE string,
lv_convexit LIKE convexit,
lo_exp TYPE REF TO cx_root,
elem_descr TYPE REF TO cl_abap_elemdescr,
table_descr TYPE REF TO cl_abap_tabledescr,
struct_descr TYPE REF TO cl_abap_structdescr,
data_descr TYPE REF TO cl_abap_datadescr.
FIELD-SYMBOLS: <line> TYPE any,
<value> TYPE any,
<data> TYPE data,
<field> LIKE LINE OF lt_fields,
<table> TYPE ANY TABLE,
<value_sym> TYPE t_s_symbol.
lv_convexit = convexit.
IF type_descr IS INITIAL AND data IS SUPPLIED.
type_descr = cl_abap_typedescr=>describe_by_data( data ).
IF mv_conversion_exits EQ abap_true AND lv_convexit IS INITIAL AND type_descr->kind EQ cl_abap_typedescr=>kind_elem.
elem_descr ?= type_descr.
lv_convexit = get_convexit_func( elem_descr = elem_descr input = abap_true ).
ENDIF.
ENDIF.
eat_white.
TRY .
IF data IS SUPPLIED AND type_descr->absolute_name EQ mc_json_type.
" skip deserialization
mark = offset.
restore_type( EXPORTING json = json length = length CHANGING offset = offset ).
match = offset - mark.
data = json+mark(match).
ELSE.
CASE json+offset(1).
WHEN `{`. " object
IF data IS SUPPLIED.
IF mv_assoc_arrays EQ c_bool-true AND type_descr->kind EQ cl_abap_typedescr=>kind_table.
table_descr ?= type_descr.
data_descr = table_descr->get_table_line_type( ).
IF table_descr->has_unique_key IS NOT INITIAL.
eat_char `{`.
eat_white.
IF json+offset(1) NE `}`.
ASSIGN data TO <table>.
CLEAR <table>.
CREATE DATA line LIKE LINE OF <table>.
ASSIGN line->* TO <line>.
lt_fields = get_fields( type_descr = data_descr data = line ).
IF table_descr->key_defkind EQ table_descr->keydefkind_user AND lines( table_descr->key ) EQ 1.
READ TABLE table_descr->key INDEX 1 INTO key_name.
READ TABLE lt_fields WITH TABLE KEY name = key_name ASSIGNING <field>.
key_ref = <field>-value.
IF mv_assoc_arrays_opt EQ c_bool-true.
struct_descr ?= data_descr.
ls_symbols = get_symbols_struct( type_descr = struct_descr data = line ).
DELETE ls_symbols-symbols WHERE name EQ key_name.
IF lines( ls_symbols-symbols ) EQ 1.
READ TABLE ls_symbols-symbols INDEX 1 ASSIGNING <value_sym>.
ENDIF.
ENDIF.
ENDIF.
eat_white.
WHILE offset < length AND json+offset(1) NE `}`.
CLEAR <line>.
eat_name key_value.
eat_white.
eat_char `:`.
eat_white.
IF <value_sym> IS ASSIGNED.
ASSIGN <value_sym>-value->* TO <value>.
restore_type( EXPORTING json = json
length = length
type_descr = <value_sym>-type
convexit = <value_sym>-convexit_in
CHANGING data = <value>
offset = offset ).
ELSE.
restore_type( EXPORTING json = json
length = length
type_descr = data_descr
field_cache = lt_fields
CHANGING data = <line>
offset = offset ).
ENDIF.
IF table_descr->key_defkind EQ table_descr->keydefkind_user.
IF key_ref IS BOUND.
ASSIGN key_ref->* TO <value>.
IF <value> IS INITIAL.
<value> = key_value.
ENDIF.
ENDIF.
ELSEIF <line> IS INITIAL.
<line> = key_value.
ENDIF.
INSERT <line> INTO TABLE <table>.
eat_white.
IF offset < length AND json+offset(1) NE `}`.
eat_char `,`.
eat_white.
ELSE.
EXIT.
ENDIF.
ENDWHILE.
ELSE.
CLEAR data.
ENDIF.
eat_char `}`.
ELSE.
restore( EXPORTING json = json length = length CHANGING offset = offset ).
ENDIF.
ELSEIF type_descr->type_kind EQ cl_abap_typedescr=>typekind_dref.
IF data IS INITIAL.
generate_int_ex( EXPORTING json = json length = length CHANGING offset = offset data = data ).
ELSE.
data_ref ?= data.
type_descr = cl_abap_typedescr=>describe_by_data_ref( data_ref ).
ASSIGN data_ref->* TO <data>.
restore_type( EXPORTING json = json length = length type_descr = type_descr CHANGING data = <data> offset = offset ).
ENDIF.
ELSE.
restore( EXPORTING json = json
length = length
type_descr = type_descr
field_cache = field_cache
CHANGING data = data
offset = offset ).
ENDIF.
ELSE.
restore( EXPORTING json = json length = length CHANGING offset = offset ).
ENDIF.
WHEN `[`. " array
IF data IS SUPPLIED AND type_descr->type_kind EQ cl_abap_typedescr=>typekind_dref.
IF data IS INITIAL.
generate_int_ex( EXPORTING json = json length = length CHANGING offset = offset data = data ).
ELSE.
data_ref ?= data.
type_descr = cl_abap_typedescr=>describe_by_data_ref( data_ref ).
ASSIGN data_ref->* TO <data>.
restore_type( EXPORTING json = json length = length type_descr = type_descr CHANGING data = <data> offset = offset ).
ENDIF.
ELSE.
eat_char `[`.
eat_white.
IF json+offset(1) NE `]`.
IF data IS SUPPLIED AND type_descr->kind EQ cl_abap_typedescr=>kind_table.
table_descr ?= type_descr.
data_descr = table_descr->get_table_line_type( ).
ASSIGN data TO <table>.
CLEAR <table>.
CREATE DATA line LIKE LINE OF <table>.
ASSIGN line->* TO <line>.
lt_fields = get_fields( type_descr = data_descr data = line ).
WHILE offset < length AND json+offset(1) NE `]`.
CLEAR <line>.
restore_type( EXPORTING json = json
length = length
type_descr = data_descr
field_cache = lt_fields
CHANGING data = <line>
offset = offset ).
INSERT <line> INTO TABLE <table>.
eat_white.
IF offset < length AND json+offset(1) NE `]`.
eat_char `,`.
eat_white.
ELSE.
EXIT.
ENDIF.
ENDWHILE.
ELSE.
" skip array
eat_white.
WHILE offset < length AND json+offset(1) NE `]`.
restore_type( EXPORTING json = json length = length CHANGING offset = offset ).
eat_white.
IF offset < length AND json+offset(1) NE `]`.
eat_char `,`.
eat_white.
ELSE.
EXIT.
ENDIF.
ENDWHILE.
IF data IS SUPPLIED. " JSON to ABAP type match error
eat_char `]`.
throw_error.
ENDIF.
ENDIF.
ELSEIF data IS SUPPLIED.
CLEAR data.
ENDIF.
eat_char `]`.
ENDIF.
WHEN `"`. " string
eat_string sdummy.
IF data IS SUPPLIED.
" unescape string
IF sdummy IS NOT INITIAL.
IF type_descr->kind EQ cl_abap_typedescr=>kind_elem.
elem_descr ?= type_descr.
IF lv_convexit IS NOT INITIAL.
TRY .
CALL FUNCTION lv_convexit
EXPORTING
input = sdummy
IMPORTING
output = data
EXCEPTIONS
error_message = 2
OTHERS = 1.
IF sy-subrc IS INITIAL.
RETURN.
ENDIF.
CATCH cx_root. "#EC NO_HANDLER
ENDTRY.
ENDIF.
CASE elem_descr->type_kind.
WHEN cl_abap_typedescr=>typekind_char.
IF elem_descr->output_length EQ 1 AND mv_bool_types CS elem_descr->absolute_name.
IF sdummy(1) CA `XxTt1`.
data = c_bool-true.
ELSE.
data = c_bool-false.
ENDIF.
RETURN.
ENDIF.
WHEN cl_abap_typedescr=>typekind_xstring.
string_to_xstring_int sdummy data.
RETURN.
WHEN cl_abap_typedescr=>typekind_hex.
" support for Edm.Guid
REPLACE FIRST OCCURRENCE OF REGEX `^([0-9A-F]{8})-([0-9A-F]{4})-([0-9A-F]{4})-([0-9A-F]{4})-([0-9A-F]{12})$` IN sdummy
WITH `$1$2$3$4$5` REPLACEMENT LENGTH match IGNORING CASE. "#EC NOTEXT
IF sy-subrc EQ 0.
sdummy = sdummy(match).
TRANSLATE sdummy TO UPPER CASE.
data = sdummy.
ELSE.
string_to_xstring_int sdummy data.
ENDIF.
RETURN.
WHEN cl_abap_typedescr=>typekind_date.
" support for ISO8601 => https://en.wikipedia.org/wiki/ISO_8601
REPLACE FIRST OCCURRENCE OF REGEX `^(\d{4})-(\d{2})-(\d{2})` IN sdummy WITH `$1$2$3`
REPLACEMENT LENGTH match. "#EC NOTEXT
IF sy-subrc EQ 0.
sdummy = sdummy(match).
ELSE.
" support for Edm.DateTime => http://www.odata.org/documentation/odata-version-2-0/json-format/
FIND FIRST OCCURRENCE OF REGEX `^\/Date\((-?\d+)([+-]\d{1,4})?\)\/` IN sdummy SUBMATCHES lv_ticks lv_offset IGNORING CASE. "#EC NOTEXT
IF sy-subrc EQ 0.
sdummy = edm_datetime_to_ts( ticks = lv_ticks offset = lv_offset typekind = elem_descr->type_kind ).
ELSE.
" support for Edm.Time => https://www.w3.org/TR/xmlschema11-2/
REPLACE FIRST OCCURRENCE OF REGEX `^-?P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)(?:\.(\d+))?S)?)?` IN sdummy WITH `$1$2$3`
REPLACEMENT LENGTH match. "#EC NOTEXT
IF sy-subrc EQ 0.
sdummy = sdummy(match).
ENDIF.
ENDIF.
ENDIF.
WHEN cl_abap_typedescr=>typekind_time.
" support for ISO8601 => https://en.wikipedia.org/wiki/ISO_8601
REPLACE FIRST OCCURRENCE OF REGEX `^(\d{2}):(\d{2}):(\d{2})` IN sdummy WITH `$1$2$3`
REPLACEMENT LENGTH match. "#EC NOTEXT
IF sy-subrc EQ 0.
sdummy = sdummy(match).
ELSE.
" support for Edm.DateTime => http://www.odata.org/documentation/odata-version-2-0/json-format/
FIND FIRST OCCURRENCE OF REGEX '^\/Date\((-?\d+)([+-]\d{1,4})?\)\/' IN sdummy SUBMATCHES lv_ticks lv_offset IGNORING CASE. "#EC NOTEXT
IF sy-subrc EQ 0.
sdummy = edm_datetime_to_ts( ticks = lv_ticks offset = lv_offset typekind = elem_descr->type_kind ).
ELSE.
" support for Edm.Time => https://www.w3.org/TR/xmlschema11-2/
REPLACE FIRST OCCURRENCE OF REGEX `^-?P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)(?:\.(\d+))?S)?)?` IN sdummy WITH `$4$5$6`
REPLACEMENT LENGTH match. "#EC NOTEXT
IF sy-subrc EQ 0.
sdummy = sdummy(match).
ENDIF.
ENDIF.
ENDIF.
WHEN cl_abap_typedescr=>typekind_packed.
REPLACE FIRST OCCURRENCE OF REGEX `^(\d{4})-?(\d{2})-?(\d{2})T(\d{2}):?(\d{2}):?(\d{2})(?:[\.,](\d{0,7}))?Z?` IN sdummy WITH `$1$2$3$4$5$6.$7`
REPLACEMENT LENGTH match. "
IF sy-subrc EQ 0.
sdummy = sdummy(match).
ELSE.
FIND FIRST OCCURRENCE OF REGEX '^\/Date\((-?\d+)([+-]\d{1,4})?\)\/' IN sdummy SUBMATCHES lv_ticks lv_offset IGNORING CASE. "#EC NOTEXT
IF sy-subrc EQ 0.
sdummy = edm_datetime_to_ts( ticks = lv_ticks offset = lv_offset typekind = elem_descr->type_kind ).
ELSE.
" support for Edm.Time => https://www.w3.org/TR/xmlschema11-2/
REPLACE FIRST OCCURRENCE OF REGEX `^-?P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)(?:\.(\d+))?S)?)?` IN sdummy WITH `$1$2$3$4$5$6.$7`
REPLACEMENT LENGTH match. "#EC NOTEXT
IF sy-subrc EQ 0.
sdummy = sdummy(match).
ENDIF.
ENDIF.
ENDIF.
WHEN `k`. "cl_abap_typedescr=>typekind_enum
TRY.
CALL METHOD ('CL_ABAP_XSD')=>('TO_VALUE')
EXPORTING
cs = sdummy
CHANGING
val = data.
RETURN.
CATCH cx_sy_dyn_call_error.
throw_error. " Deserialization of enums is not supported
ENDTRY.
ENDCASE.
ELSEIF type_descr->type_kind EQ cl_abap_typedescr=>typekind_dref.
CREATE DATA rdummy TYPE string.
ASSIGN rdummy->* TO <data>.
<data> = sdummy.
data ?= rdummy.
RETURN.
ELSE.
throw_error. " Other wise dumps with OBJECTS_MOVE_NOT_SUPPORTED
ENDIF.
data = sdummy.
ELSEIF type_descr->kind EQ cl_abap_typedescr=>kind_elem.
CLEAR data.
ELSE.
throw_error. " Other wise dumps with OBJECTS_MOVE_NOT_SUPPORTED
ENDIF.
ENDIF.
WHEN `-` OR `0` OR `1` OR `2` OR `3` OR `4` OR `5` OR `6` OR `7` OR `8` OR `9`. " number
IF data IS SUPPLIED.
IF type_descr->kind EQ type_descr->kind_ref AND type_descr->type_kind EQ cl_abap_typedescr=>typekind_dref.
eat_number sdummy. "#EC NOTEXT
match = strlen( sdummy ).
IF sdummy CS '.'. " float.
CREATE DATA rdummy TYPE f.
ELSEIF match GT 9. " packed
CREATE DATA rdummy TYPE p.
ELSE. " integer
CREATE DATA rdummy TYPE i.
ENDIF.
ASSIGN rdummy->* TO <data>.
<data> = sdummy.
data ?= rdummy.
ELSEIF type_descr->kind EQ type_descr->kind_elem.
IF lv_convexit IS NOT INITIAL.
TRY .
eat_number sdummy. "#EC NOTEXT
CALL FUNCTION lv_convexit
EXPORTING
input = sdummy
IMPORTING
output = data
EXCEPTIONS
error_message = 2
OTHERS = 1.
IF sy-subrc IS INITIAL.
RETURN.
ENDIF.
CATCH cx_root. "#EC NO_HANDLER
ENDTRY.
ENDIF.
eat_number data. "#EC NOTEXT
ELSE.
eat_number sdummy. "#EC NOTEXT
throw_error.
ENDIF.
ELSE.
eat_number sdummy. "#EC NOTEXT
ENDIF.
WHEN OTHERS. " boolean, e.g true/false/null
IF data IS SUPPLIED.
IF type_descr->kind EQ type_descr->kind_ref AND type_descr->type_kind EQ cl_abap_typedescr=>typekind_dref.
CREATE DATA rdummy TYPE bool.
ASSIGN rdummy->* TO <data>.
eat_bool <data>. "#EC NOTEXT
data ?= rdummy.
ELSEIF type_descr->kind EQ type_descr->kind_elem.
eat_bool data. "#EC NOTEXT
ELSE.
eat_bool sdummy. "#EC NOTEXT
throw_error.
ENDIF.
ELSE.
eat_bool sdummy. "#EC NOTEXT
ENDIF.
ENDCASE.
ENDIF.
CATCH cx_sy_move_cast_error cx_sy_conversion_no_number cx_sy_conversion_overflow INTO lo_exp.
CLEAR data.
IF mv_strict_mode EQ abap_true.
RAISE EXCEPTION TYPE cx_sy_move_cast_error EXPORTING previous = lo_exp.
ENDIF.
ENDTRY.
ENDMETHOD. "restore_type
METHOD serialize.
DATA: lo_json TYPE REF TO zcl_json.
CREATE OBJECT lo_json
EXPORTING
compress = compress
pretty_name = pretty_name
name_mappings = name_mappings
assoc_arrays = assoc_arrays
assoc_arrays_opt = assoc_arrays_opt
expand_includes = expand_includes
numc_as_string = numc_as_string
conversion_exits = conversion_exits
format_output = format_output
hex_as_base64 = hex_as_base64
ts_as_iso8601 = ts_as_iso8601.
r_json = lo_json->serialize_int( name = name data = data type_descr = type_descr ).
ENDMETHOD. "serialize
METHOD serialize_int.
DATA: lo_descr TYPE REF TO cl_abap_typedescr,
lo_elem_descr TYPE REF TO cl_abap_elemdescr,
lv_convexit TYPE string.
IF type_descr IS INITIAL.
lo_descr = cl_abap_typedescr=>describe_by_data( data ).
ELSE.
lo_descr = type_descr.
ENDIF.
IF mv_conversion_exits EQ abap_true AND lo_descr->kind EQ cl_abap_typedescr=>kind_elem.
lo_elem_descr ?= lo_descr.
lv_convexit = get_convexit_func( elem_descr = lo_elem_descr input = abap_false ).
ENDIF.
r_json = dump_int( data = data type_descr = lo_descr convexit = lv_convexit ).
IF name IS NOT INITIAL AND ( mv_compress IS INITIAL OR r_json IS NOT INITIAL ).
CONCATENATE `"` name `":` r_json INTO r_json.
ENDIF.
ENDMETHOD. "serialize
METHOD string_to_raw.
CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
EXPORTING
text = iv_string
encoding = iv_encoding
IMPORTING
buffer = rv_xstring
EXCEPTIONS
OTHERS = 1.
IF sy-subrc IS NOT INITIAL.
CLEAR rv_xstring.
ENDIF.
ENDMETHOD.
METHOD string_to_xstring.
DATA: lv_xstring TYPE xstring.
CALL FUNCTION 'SSFC_BASE64_DECODE'
EXPORTING
b64data = in
IMPORTING
bindata = lv_xstring
EXCEPTIONS
OTHERS = 1.
IF sy-subrc IS INITIAL.
out = lv_xstring.
ELSE.
out = in.
ENDIF.
ENDMETHOD. "string_to_xstring
METHOD tribool_to_bool.
IF iv_tribool EQ c_tribool-true.
rv_bool = c_bool-true.
ELSEIF iv_tribool EQ c_tribool-undefined.
rv_bool = abap_undefined. " fall back to abap_undefined
ENDIF.
ENDMETHOD. "TRIBOOL_TO_BOOL
METHOD unescape.
DATA: lv_offset TYPE i,
lv_match TYPE i,
lv_delta TYPE i,
lv_length TYPE i,
lv_offset_e TYPE i,
lv_length_e TYPE i,
lv_unicode_symb TYPE c,
lv_unicode_escaped TYPE string,
lt_matches TYPE match_result_tab.
FIELD-SYMBOLS: <match> LIKE LINE OF lt_matches.
" see reference for escaping rules in JSON RFC
" https://www.ietf.org/rfc/rfc4627.txt
unescaped = escaped.
lv_length = strlen( unescaped ).
FIND FIRST OCCURRENCE OF REGEX `\\[rntfbu]` IN unescaped RESPECTING CASE.
IF sy-subrc IS INITIAL.
FIND ALL OCCURRENCES OF REGEX `\\.` IN unescaped RESULTS lt_matches RESPECTING CASE.
LOOP AT lt_matches ASSIGNING <match>.
lv_match = <match>-offset - lv_delta.
lv_offset = lv_match + 1.
CASE unescaped+lv_offset(1).
WHEN `r`.
REPLACE SECTION OFFSET lv_match LENGTH 2 OF unescaped WITH cl_abap_char_utilities=>cr_lf(1).
lv_delta = lv_delta + 1.
WHEN `n`.
REPLACE SECTION OFFSET lv_match LENGTH 2 OF unescaped WITH cl_abap_char_utilities=>newline.
lv_delta = lv_delta + 1.
WHEN `t`.
REPLACE SECTION OFFSET lv_match LENGTH 2 OF unescaped WITH cl_abap_char_utilities=>horizontal_tab.
lv_delta = lv_delta + 1.
WHEN `f`.
REPLACE SECTION OFFSET lv_match LENGTH 2 OF unescaped WITH cl_abap_char_utilities=>form_feed.
lv_delta = lv_delta + 1.
WHEN `b`.
REPLACE SECTION OFFSET lv_match LENGTH 2 OF unescaped WITH cl_abap_char_utilities=>backspace.
lv_delta = lv_delta + 1.
WHEN `u`.
lv_offset = lv_offset + 1.
lv_offset_e = lv_offset + 4.
lv_length_e = lv_length + lv_delta.
IF lv_offset_e LE lv_length_e.
lv_unicode_escaped = unescaped+lv_offset(4).
TRANSLATE lv_unicode_escaped TO UPPER CASE.
lv_unicode_symb = cl_abap_conv_in_ce=>uccp( lv_unicode_escaped ).
IF lv_unicode_symb NE mc_cov_error.
REPLACE SECTION OFFSET lv_match LENGTH 6 OF unescaped WITH lv_unicode_symb.
lv_delta = lv_delta + 5.
ENDIF.
ENDIF.
ENDCASE.
ENDLOOP.
ENDIF.
" based on RFC mentioned above, _any_ character can be escaped, and so shall be enscaped
" the only exception is Unicode symbols, that shall be kept untouched, while serializer does not handle them
" unescaped singe characters, e.g \\, \", \/ etc
REPLACE ALL OCCURRENCES OF REGEX `\\(.)` IN unescaped WITH `$1` RESPECTING CASE.
ENDMETHOD.
METHOD xstring_to_string.
DATA: lv_xstring TYPE xstring.
" let us fix data conversion issues here
lv_xstring = in.
CALL FUNCTION 'SSFC_BASE64_ENCODE'
EXPORTING
bindata = lv_xstring
IMPORTING
b64data = out
EXCEPTIONS
OTHERS = 1.
IF sy-subrc IS NOT INITIAL.
out = in.
ENDIF.
ENDMETHOD. "xstring_to_string
METHOD generate_struct.
DATA: lv_comp_name TYPE abap_compname,
lt_comp TYPE abap_component_tab,
lt_keys TYPE STANDARD TABLE OF string,
lv_invalid TYPE abap_bool,
ls_type LIKE LINE OF mt_struct_type,
lt_names TYPE HASHED TABLE OF string WITH UNIQUE KEY table_line,
cache LIKE LINE OF mt_name_mappings_ex,
ls_comp LIKE LINE OF lt_comp.
FIELD-SYMBOLS: <field> LIKE LINE OF fields,
<cache> LIKE LINE OF mt_name_mappings_ex.
CHECK fields IS NOT INITIAL.
" prepare structure type key
LOOP AT fields ASSIGNING <field>.
APPEND <field>-name TO lt_keys.
ENDLOOP.
CONCATENATE LINES OF lt_keys INTO ls_type-keys.
READ TABLE mt_struct_type WITH TABLE KEY keys = ls_type-keys INTO ls_type.
IF sy-subrc IS NOT INITIAL.
ls_comp-type = cl_abap_refdescr=>get_ref_to_data( ).
LOOP AT fields ASSIGNING <field>.
READ TABLE mt_name_mappings_ex WITH TABLE KEY json = <field>-name ASSIGNING <cache>.
IF sy-subrc IS INITIAL.
ls_comp-name = <cache>-abap.
ELSE.
cache-json = ls_comp-name = <field>-name.
" remove characters not allowed in component names
TRANSLATE ls_comp-name USING mc_name_symbols_map.
IF mv_pretty_name EQ pretty_mode-camel_case OR mv_pretty_name EQ pretty_mode-extended.
REPLACE ALL OCCURRENCES OF REGEX `([a-z])([A-Z])` IN ls_comp-name WITH `$1_$2`. "#EC NOTEXT
ENDIF.
TRANSLATE ls_comp-name TO UPPER CASE.
cache-abap = ls_comp-name = lv_comp_name = ls_comp-name. " truncate by allowed field name length
INSERT cache INTO TABLE mt_name_mappings_ex.
ENDIF.
INSERT ls_comp-name INTO TABLE lt_names.
IF sy-subrc IS INITIAL.
APPEND ls_comp TO lt_comp.
ELSE.
DELETE fields.
lv_invalid = abap_true.
ENDIF.
ENDLOOP.
TRY.
ls_type-type = cl_abap_structdescr=>create( p_components = lt_comp p_strict = c_bool-false ).
CATCH cx_sy_struct_creation. "
ENDTRY.
IF lv_invalid EQ abap_false.
INSERT ls_type INTO TABLE mt_struct_type.
ENDIF.
ENDIF.
IF ls_type-type IS NOT INITIAL.
TRY.
CREATE DATA data TYPE HANDLE ls_type-type.
CATCH cx_sy_create_data_error. "#EC NO_HANDLER
ENDTRY.
ENDIF.
ENDMETHOD.
METHOD get_indent.
STATICS: st_indent TYPE STANDARD TABLE OF string WITH DEFAULT KEY.
DATA: lv_filled TYPE i.
READ TABLE st_indent INDEX level INTO indent.
IF sy-subrc IS NOT INITIAL.
lv_filled = lines( st_indent ).
indent = cl_abap_char_utilities=>cr_lf.
DO level TIMES.
CONCATENATE indent mc_default_indent INTO indent.
IF sy-index GT lv_filled.
APPEND indent TO st_indent.
ENDIF.
ENDDO.
ENDIF.
ENDMETHOD.
METHOD get_symbols_class.
DATA: symb LIKE LINE OF result.
FIELD-SYMBOLS: <attr> LIKE LINE OF cl_abap_objectdescr=>attributes,
<cache> LIKE LINE OF mt_name_mappings,
<field> TYPE any.
LOOP AT type_descr->attributes ASSIGNING <attr> WHERE is_constant IS INITIAL AND alias_for IS INITIAL AND
( is_interface IS INITIAL OR type_kind NE cl_abap_typedescr=>typekind_oref ).
ASSIGN object->(<attr>-name) TO <field>.
CHECK sy-subrc IS INITIAL. " we can only assign to public attributes
symb-name = <attr>-name.
symb-read_only = <attr>-is_read_only.
symb-type = type_descr->get_attribute_type( <attr>-name ).
IF symb-type->kind EQ cl_abap_typedescr=>kind_elem.
symb-elem_type ?= symb-type.
ELSE.
CLEAR symb-elem_type.
ENDIF.
IF mv_conversion_exits EQ abap_true AND symb-elem_type IS NOT INITIAL.
symb-convexit_in = get_convexit_func( elem_descr = symb-elem_type input = abap_true ).
symb-convexit_out = get_convexit_func( elem_descr = symb-elem_type input = abap_false ).
ENDIF.
is_compressable symb-type symb-name symb-compressable.
GET REFERENCE OF <field> INTO symb-value.
format_name symb-name mv_pretty_name symb-header.
CONCATENATE `"` symb-header `":` INTO symb-header.
IF mv_format_output EQ abap_true.
CONCATENATE symb-header ` ` INTO symb-header.
ENDIF.
APPEND symb TO result.
ENDLOOP.
ENDMETHOD. "GET_SYMBOLS
METHOD get_symbols_struct.
DATA: comp_tab TYPE cl_abap_structdescr=>component_table,
sym_cache LIKE result,
symbol TYPE t_s_symbol,
struct_descr TYPE REF TO cl_abap_structdescr,
struct_cache LIKE LINE OF mt_struct_cache.
FIELD-SYMBOLS: <comp> LIKE LINE OF comp_tab,
<symbol> LIKE symbol,
<cache> LIKE LINE OF mt_name_mappings,
<struct> LIKE LINE OF mt_struct_cache,
<data> TYPE data,
<field> TYPE any.
READ TABLE mt_struct_cache WITH TABLE KEY type_descr = type_descr include_aliases = include_aliases level = level
ASSIGNING <struct>.
IF sy-subrc IS NOT INITIAL.
struct_cache-type_descr = type_descr.
struct_cache-include_aliases = include_aliases.
struct_cache-level = level.
CREATE DATA struct_cache-result-data TYPE HANDLE type_descr.
INSERT struct_cache INTO TABLE mt_struct_cache ASSIGNING <struct>.
ASSIGN <struct>-result-data->* TO <data>.
comp_tab = type_descr->get_components( ).
LOOP AT comp_tab ASSIGNING <comp>.
IF <comp>-name IS NOT INITIAL AND
( <comp>-as_include EQ abap_false OR include_aliases EQ abap_true OR mv_expand_includes EQ abap_false ).
symbol-name = <comp>-name.
symbol-type = <comp>-type.
IF symbol-type->kind EQ cl_abap_typedescr=>kind_elem.
symbol-elem_type ?= symbol-type.
ELSE.
CLEAR symbol-elem_type.
ENDIF.
IF mv_conversion_exits EQ abap_true AND symbol-elem_type IS NOT INITIAL.
symbol-convexit_in = get_convexit_func( elem_descr = symbol-elem_type input = abap_true ).
symbol-convexit_out = get_convexit_func( elem_descr = symbol-elem_type input = abap_false ).
ENDIF.
is_compressable symbol-type symbol-name symbol-compressable.
ASSIGN COMPONENT symbol-name OF STRUCTURE <data> TO <field>.
GET REFERENCE OF <field> INTO symbol-value.
format_name symbol-name mv_pretty_name symbol-header.
CONCATENATE `"` symbol-header `":` INTO symbol-header.
IF mv_format_output EQ abap_true.
CONCATENATE symbol-header ` ` INTO symbol-header.
ENDIF.
APPEND symbol TO <struct>-result-symbols.
ENDIF.
IF <comp>-as_include EQ abap_true AND mv_expand_includes EQ abap_true.
struct_descr ?= <comp>-type.
sym_cache = get_symbols_struct( type_descr = struct_descr include_aliases = include_aliases ).
LOOP AT sym_cache-symbols INTO symbol.
CONCATENATE symbol-name <comp>-suffix INTO symbol-name.
IF symbol-type->kind EQ cl_abap_typedescr=>kind_elem.
symbol-elem_type ?= symbol-type.
ELSE.
CLEAR symbol-elem_type.
ENDIF.
IF mv_conversion_exits EQ abap_true AND symbol-elem_type IS NOT INITIAL.
symbol-convexit_in = get_convexit_func( elem_descr = symbol-elem_type input = abap_true ).
symbol-convexit_out = get_convexit_func( elem_descr = symbol-elem_type input = abap_false ).
ENDIF.
is_compressable symbol-type symbol-name symbol-compressable.
ASSIGN COMPONENT symbol-name OF STRUCTURE <data> TO <field>.
GET REFERENCE OF <field> INTO symbol-value.
format_name symbol-name mv_pretty_name symbol-header.
CONCATENATE `"` symbol-header `":` INTO symbol-header.
IF mv_format_output EQ abap_true.
CONCATENATE symbol-header ` ` INTO symbol-header.
ENDIF.
APPEND symbol TO <struct>-result-symbols.
ENDLOOP.
ENDIF.
ENDLOOP.
ENDIF.
result = <struct>-result.
IF data IS BOUND AND data NE <struct>-result-data.
result-data = data.
ASSIGN data->* TO <data>.
LOOP AT result-symbols ASSIGNING <symbol>.
ASSIGN COMPONENT <symbol>-name OF STRUCTURE <data> TO <field>.
GET REFERENCE OF <field> INTO <symbol>-value.
ENDLOOP.
ENDIF.
ENDMETHOD. "GET_SYMBOLS_STRUCT
ENDCLASS.