abapGit/src/json/zcl_abapgit_ajson.clas.abap
github-actions[bot] b10276ffa5
ajson, Automatic Update (#6341)
Co-authored-by: larshp <larshp@users.noreply.github.com>
Co-authored-by: Lars Hvam <larshp@hotmail.com>
2023-07-11 09:09:50 +02:00

966 lines
27 KiB
ABAP

CLASS zcl_abapgit_ajson DEFINITION
PUBLIC
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES zif_abapgit_ajson .
ALIASES:
is_empty FOR zif_abapgit_ajson~is_empty,
exists FOR zif_abapgit_ajson~exists,
members FOR zif_abapgit_ajson~members,
get FOR zif_abapgit_ajson~get,
get_boolean FOR zif_abapgit_ajson~get_boolean,
get_integer FOR zif_abapgit_ajson~get_integer,
get_number FOR zif_abapgit_ajson~get_number,
get_date FOR zif_abapgit_ajson~get_date,
get_timestamp FOR zif_abapgit_ajson~get_timestamp,
get_string FOR zif_abapgit_ajson~get_string,
slice FOR zif_abapgit_ajson~slice,
to_abap FOR zif_abapgit_ajson~to_abap,
array_to_string_table FOR zif_abapgit_ajson~array_to_string_table.
ALIASES:
clear FOR zif_abapgit_ajson~clear,
set FOR zif_abapgit_ajson~set,
setx FOR zif_abapgit_ajson~setx,
set_boolean FOR zif_abapgit_ajson~set_boolean,
set_string FOR zif_abapgit_ajson~set_string,
set_integer FOR zif_abapgit_ajson~set_integer,
set_date FOR zif_abapgit_ajson~set_date,
set_timestamp FOR zif_abapgit_ajson~set_timestamp,
set_null FOR zif_abapgit_ajson~set_null,
delete FOR zif_abapgit_ajson~delete,
touch_array FOR zif_abapgit_ajson~touch_array,
push FOR zif_abapgit_ajson~push,
stringify FOR zif_abapgit_ajson~stringify.
ALIASES:
clone FOR zif_abapgit_ajson~clone,
filter FOR zif_abapgit_ajson~filter,
map FOR zif_abapgit_ajson~map.
ALIASES:
mt_json_tree FOR zif_abapgit_ajson~mt_json_tree,
keep_item_order FOR zif_abapgit_ajson~keep_item_order,
format_datetime FOR zif_abapgit_ajson~format_datetime,
to_abap_corresponding_only FOR zif_abapgit_ajson~to_abap_corresponding_only,
freeze FOR zif_abapgit_ajson~freeze.
CLASS-METHODS parse
IMPORTING
!iv_json TYPE string
!iv_freeze TYPE abap_bool DEFAULT abap_false
!ii_custom_mapping TYPE REF TO zif_abapgit_ajson_mapping OPTIONAL
!iv_keep_item_order TYPE abap_bool DEFAULT abap_false
RETURNING
VALUE(ro_instance) TYPE REF TO zcl_abapgit_ajson
RAISING
zcx_abapgit_ajson_error .
CLASS-METHODS create_empty " Might be deprecated, prefer using new( ) or create object
IMPORTING
!ii_custom_mapping TYPE REF TO zif_abapgit_ajson_mapping OPTIONAL
iv_keep_item_order TYPE abap_bool DEFAULT abap_false
iv_format_datetime TYPE abap_bool DEFAULT abap_true
iv_to_abap_corresponding_only TYPE abap_bool DEFAULT abap_false
RETURNING
VALUE(ro_instance) TYPE REF TO zcl_abapgit_ajson.
" Experimental ! May change
CLASS-METHODS create_from " TODO, rename to 'from' ?
IMPORTING
!ii_source_json TYPE REF TO zif_abapgit_ajson
!ii_filter TYPE REF TO zif_abapgit_ajson_filter OPTIONAL " Might be deprecated, use filter() instead
!ii_mapper TYPE REF TO zif_abapgit_ajson_mapping OPTIONAL " Might be deprecated, use map() instead
RETURNING
VALUE(ro_instance) TYPE REF TO zcl_abapgit_ajson
RAISING
zcx_abapgit_ajson_error .
METHODS constructor
IMPORTING
iv_keep_item_order TYPE abap_bool DEFAULT abap_false
iv_format_datetime TYPE abap_bool DEFAULT abap_true
iv_to_abap_corresponding_only TYPE abap_bool DEFAULT abap_false.
CLASS-METHODS new
IMPORTING
iv_keep_item_order TYPE abap_bool DEFAULT abap_false
iv_format_datetime TYPE abap_bool DEFAULT abap_true
iv_to_abap_corresponding_only TYPE abap_bool DEFAULT abap_false
RETURNING
VALUE(ro_instance) TYPE REF TO zcl_abapgit_ajson.
PROTECTED SECTION.
PRIVATE SECTION.
CLASS-DATA go_float_regex TYPE REF TO cl_abap_regex.
DATA ms_opts TYPE zif_abapgit_ajson=>ty_opts.
DATA mi_custom_mapping TYPE REF TO zif_abapgit_ajson_mapping. " DEPRECATED, will be removed
METHODS get_item
IMPORTING
iv_path TYPE string
RETURNING
VALUE(rv_item) TYPE REF TO zif_abapgit_ajson_types=>ty_node.
METHODS prove_path_exists
IMPORTING
iv_path TYPE string
RETURNING
VALUE(rr_end_node) TYPE REF TO zif_abapgit_ajson_types=>ty_node
RAISING
zcx_abapgit_ajson_error.
METHODS delete_subtree
IMPORTING
iv_path TYPE string
iv_name TYPE string
ir_parent TYPE REF TO zif_abapgit_ajson_types=>ty_node OPTIONAL
RETURNING
VALUE(rs_top_node) TYPE zif_abapgit_ajson_types=>ty_node.
METHODS read_only_watchdog
RAISING
zcx_abapgit_ajson_error.
ENDCLASS.
CLASS zcl_abapgit_ajson IMPLEMENTATION.
METHOD constructor.
ms_opts-keep_item_order = iv_keep_item_order.
ms_opts-to_abap_corresponding_only = iv_to_abap_corresponding_only.
format_datetime( iv_format_datetime ).
ENDMETHOD.
METHOD create_empty.
CREATE OBJECT ro_instance
EXPORTING
iv_to_abap_corresponding_only = iv_to_abap_corresponding_only
iv_format_datetime = iv_format_datetime
iv_keep_item_order = iv_keep_item_order.
ro_instance->mi_custom_mapping = ii_custom_mapping.
ENDMETHOD.
METHOD create_from.
DATA lo_mutator_queue TYPE REF TO lcl_mutator_queue.
IF ii_source_json IS NOT BOUND.
zcx_abapgit_ajson_error=>raise( 'Source not bound' ).
ENDIF.
CREATE OBJECT ro_instance
EXPORTING
iv_to_abap_corresponding_only = ii_source_json->opts( )-to_abap_corresponding_only
iv_format_datetime = ii_source_json->opts( )-format_datetime
iv_keep_item_order = ii_source_json->opts( )-keep_item_order.
IF ii_filter IS NOT BOUND AND ii_mapper IS NOT BOUND.
ro_instance->mt_json_tree = ii_source_json->mt_json_tree.
ELSE.
CREATE OBJECT lo_mutator_queue.
IF ii_mapper IS BOUND.
" Mapping goes first. But maybe it should be a freely definable queue of processors ?
lo_mutator_queue->add( lcl_mapper_runner=>new( ii_mapper ) ).
ENDIF.
IF ii_filter IS BOUND.
lo_mutator_queue->add( lcl_filter_runner=>new( ii_filter ) ).
ENDIF.
lo_mutator_queue->lif_mutator_runner~run(
EXPORTING
it_source_tree = ii_source_json->mt_json_tree
IMPORTING
et_dest_tree = ro_instance->mt_json_tree ).
ENDIF.
ENDMETHOD.
METHOD delete_subtree.
DATA lv_parent_path TYPE string.
DATA lr_parent LIKE ir_parent.
READ TABLE mt_json_tree INTO rs_top_node
WITH KEY
path = iv_path
name = iv_name.
IF sy-subrc <> 0.
RETURN. " Not found ? nothing to delete !
ENDIF.
DELETE mt_json_tree INDEX sy-tabix. " where path = iv_path and name = iv_name.
IF rs_top_node-children > 0. " only for objects and arrays
lv_parent_path = iv_path && iv_name && '/*'.
DELETE mt_json_tree WHERE path CP lv_parent_path.
ENDIF.
" decrement parent children
IF ir_parent IS SUPPLIED.
ir_parent->children = ir_parent->children - 1.
ELSE.
lr_parent = get_item( iv_path ).
IF lr_parent IS NOT INITIAL.
lr_parent->children = lr_parent->children - 1.
ENDIF.
ENDIF.
ENDMETHOD.
METHOD get_item.
FIELD-SYMBOLS <item> LIKE LINE OF mt_json_tree.
DATA ls_path_name TYPE zif_abapgit_ajson_types=>ty_path_name.
ls_path_name = lcl_utils=>split_path( iv_path ).
READ TABLE mt_json_tree
ASSIGNING <item>
WITH KEY
path = ls_path_name-path
name = ls_path_name-name.
IF sy-subrc = 0.
GET REFERENCE OF <item> INTO rv_item.
ENDIF.
ENDMETHOD.
METHOD new.
CREATE OBJECT ro_instance
EXPORTING
iv_to_abap_corresponding_only = iv_to_abap_corresponding_only
iv_format_datetime = iv_format_datetime
iv_keep_item_order = iv_keep_item_order.
ENDMETHOD.
METHOD parse.
DATA lo_parser TYPE REF TO lcl_json_parser.
CREATE OBJECT ro_instance.
CREATE OBJECT lo_parser.
ro_instance->mt_json_tree = lo_parser->parse(
iv_json = iv_json
iv_keep_item_order = iv_keep_item_order ).
ro_instance->mi_custom_mapping = ii_custom_mapping.
ro_instance->ms_opts-keep_item_order = iv_keep_item_order.
IF iv_freeze = abap_true.
ro_instance->freeze( ).
ENDIF.
ENDMETHOD.
METHOD prove_path_exists.
DATA lt_path TYPE string_table.
DATA lr_node_parent LIKE rr_end_node.
DATA lv_cur_path TYPE string.
DATA lv_cur_name TYPE string.
DATA ls_new_node LIKE LINE OF mt_json_tree.
SPLIT iv_path AT '/' INTO TABLE lt_path.
DELETE lt_path WHERE table_line IS INITIAL.
DO.
lr_node_parent = rr_end_node.
READ TABLE mt_json_tree REFERENCE INTO rr_end_node
WITH KEY
path = lv_cur_path
name = lv_cur_name.
IF sy-subrc <> 0. " New node, assume it is always object as it has a named child, use touch_array to init array
CLEAR ls_new_node.
IF lr_node_parent IS NOT INITIAL. " if has parent
lr_node_parent->children = lr_node_parent->children + 1.
IF lr_node_parent->type = zif_abapgit_ajson_types=>node_type-array.
ls_new_node-index = lcl_utils=>validate_array_index(
iv_path = lv_cur_path
iv_index = lv_cur_name ).
ENDIF.
ENDIF.
ls_new_node-path = lv_cur_path.
ls_new_node-name = lv_cur_name.
ls_new_node-type = zif_abapgit_ajson_types=>node_type-object.
INSERT ls_new_node INTO TABLE mt_json_tree REFERENCE INTO rr_end_node.
ENDIF.
lv_cur_path = lv_cur_path && lv_cur_name && '/'.
READ TABLE lt_path INDEX sy-index INTO lv_cur_name.
IF sy-subrc <> 0.
EXIT. " no more segments
ENDIF.
ENDDO.
ENDMETHOD.
METHOD read_only_watchdog.
IF ms_opts-read_only = abap_true.
zcx_abapgit_ajson_error=>raise( 'This json instance is read only' ).
ENDIF.
ENDMETHOD.
METHOD zif_abapgit_ajson~array_to_string_table.
DATA lv_normalized_path TYPE string.
DATA lr_node TYPE REF TO zif_abapgit_ajson_types=>ty_node.
FIELD-SYMBOLS <item> LIKE LINE OF mt_json_tree.
lv_normalized_path = lcl_utils=>normalize_path( iv_path ).
lr_node = get_item( iv_path ).
IF lr_node IS INITIAL.
zcx_abapgit_ajson_error=>raise( |Path not found: { iv_path }| ).
ENDIF.
IF lr_node->type <> zif_abapgit_ajson_types=>node_type-array.
zcx_abapgit_ajson_error=>raise( |Array expected at: { iv_path }| ).
ENDIF.
LOOP AT mt_json_tree ASSIGNING <item> WHERE path = lv_normalized_path.
CASE <item>-type.
WHEN zif_abapgit_ajson_types=>node_type-number OR zif_abapgit_ajson_types=>node_type-string.
APPEND <item>-value TO rt_string_table.
WHEN zif_abapgit_ajson_types=>node_type-null.
APPEND '' TO rt_string_table.
WHEN zif_abapgit_ajson_types=>node_type-boolean.
DATA lv_tmp TYPE string.
IF <item>-value = 'true'.
lv_tmp = abap_true.
ELSE.
CLEAR lv_tmp.
ENDIF.
APPEND lv_tmp TO rt_string_table.
WHEN OTHERS.
zcx_abapgit_ajson_error=>raise( |Cannot convert [{ <item>-type
}] to string at [{ <item>-path }{ <item>-name }]| ).
ENDCASE.
ENDLOOP.
ENDMETHOD.
METHOD zif_abapgit_ajson~clear.
read_only_watchdog( ).
CLEAR mt_json_tree.
ENDMETHOD.
METHOD zif_abapgit_ajson~clone.
ri_json = create_from( me ).
ENDMETHOD.
METHOD zif_abapgit_ajson~delete.
read_only_watchdog( ).
DATA ls_split_path TYPE zif_abapgit_ajson_types=>ty_path_name.
ls_split_path = lcl_utils=>split_path( iv_path ).
delete_subtree(
iv_path = ls_split_path-path
iv_name = ls_split_path-name ).
ri_json = me.
ENDMETHOD.
METHOD zif_abapgit_ajson~exists.
rv_exists = boolc( get_item( iv_path ) IS NOT INITIAL ).
ENDMETHOD.
METHOD zif_abapgit_ajson~filter.
ri_json = create_from(
ii_source_json = me
ii_filter = ii_filter ).
ENDMETHOD.
METHOD zif_abapgit_ajson~format_datetime.
ms_opts-format_datetime = iv_use_iso.
ri_json = me.
ENDMETHOD.
METHOD zif_abapgit_ajson~freeze.
ms_opts-read_only = abap_true.
ENDMETHOD.
METHOD zif_abapgit_ajson~get.
DATA lr_item TYPE REF TO zif_abapgit_ajson_types=>ty_node.
lr_item = get_item( iv_path ).
IF lr_item IS NOT INITIAL.
rv_value = lr_item->value.
ENDIF.
ENDMETHOD.
METHOD zif_abapgit_ajson~get_boolean.
DATA lr_item TYPE REF TO zif_abapgit_ajson_types=>ty_node.
lr_item = get_item( iv_path ).
IF lr_item IS INITIAL OR lr_item->type = zif_abapgit_ajson_types=>node_type-null.
RETURN.
ELSEIF lr_item->type = zif_abapgit_ajson_types=>node_type-boolean.
rv_value = boolc( lr_item->value = 'true' ).
ELSEIF lr_item->value IS NOT INITIAL.
rv_value = abap_true.
ENDIF.
ENDMETHOD.
METHOD zif_abapgit_ajson~get_date.
DATA lr_item TYPE REF TO zif_abapgit_ajson_types=>ty_node.
DATA lv_y TYPE c LENGTH 4.
DATA lv_m TYPE c LENGTH 2.
DATA lv_d TYPE c LENGTH 2.
lr_item = get_item( iv_path ).
IF lr_item IS NOT INITIAL AND lr_item->type = zif_abapgit_ajson_types=>node_type-string.
FIND FIRST OCCURRENCE OF REGEX '^(\d{4})-(\d{2})-(\d{2})(T|$)'
IN lr_item->value
SUBMATCHES lv_y lv_m lv_d.
CONCATENATE lv_y lv_m lv_d INTO rv_value.
ENDIF.
ENDMETHOD.
METHOD zif_abapgit_ajson~get_integer.
DATA lr_item TYPE REF TO zif_abapgit_ajson_types=>ty_node.
lr_item = get_item( iv_path ).
IF lr_item IS NOT INITIAL AND lr_item->type = zif_abapgit_ajson_types=>node_type-number.
rv_value = lr_item->value.
ENDIF.
ENDMETHOD.
METHOD zif_abapgit_ajson~get_node_type.
DATA lr_item TYPE REF TO zif_abapgit_ajson_types=>ty_node.
lr_item = get_item( iv_path ).
IF lr_item IS NOT INITIAL.
rv_node_type = lr_item->type.
ENDIF.
ENDMETHOD.
METHOD zif_abapgit_ajson~get_number.
DATA lr_item TYPE REF TO zif_abapgit_ajson_types=>ty_node.
lr_item = get_item( iv_path ).
IF lr_item IS NOT INITIAL AND lr_item->type = zif_abapgit_ajson_types=>node_type-number.
rv_value = lr_item->value.
ENDIF.
ENDMETHOD.
METHOD zif_abapgit_ajson~get_string.
DATA lr_item TYPE REF TO zif_abapgit_ajson_types=>ty_node.
lr_item = get_item( iv_path ).
IF lr_item IS NOT INITIAL AND lr_item->type <> zif_abapgit_ajson_types=>node_type-null.
rv_value = lr_item->value.
ENDIF.
ENDMETHOD.
METHOD zif_abapgit_ajson~get_timestamp.
DATA lo_to_abap TYPE REF TO lcl_json_to_abap.
DATA lr_item TYPE REF TO zif_abapgit_ajson_types=>ty_node.
lr_item = get_item( iv_path ).
IF lr_item IS INITIAL.
RETURN.
ENDIF.
CREATE OBJECT lo_to_abap.
TRY.
rv_value = lo_to_abap->to_timestamp( lr_item->value ).
CATCH zcx_abapgit_ajson_error.
RETURN.
ENDTRY.
ENDMETHOD.
METHOD zif_abapgit_ajson~is_empty.
rv_yes = boolc( lines( mt_json_tree ) = 0 ).
ENDMETHOD.
METHOD zif_abapgit_ajson~keep_item_order.
ms_opts-keep_item_order = abap_true.
ri_json = me.
ENDMETHOD.
METHOD zif_abapgit_ajson~map.
ri_json = create_from(
ii_source_json = me
ii_mapper = ii_mapper ).
ENDMETHOD.
METHOD zif_abapgit_ajson~members.
DATA lv_normalized_path TYPE string.
FIELD-SYMBOLS <item> LIKE LINE OF mt_json_tree.
lv_normalized_path = lcl_utils=>normalize_path( iv_path ).
LOOP AT mt_json_tree ASSIGNING <item> WHERE path = lv_normalized_path.
APPEND <item>-name TO rt_members.
ENDLOOP.
ENDMETHOD.
METHOD zif_abapgit_ajson~opts.
rs_opts = ms_opts.
ENDMETHOD.
METHOD zif_abapgit_ajson~push.
DATA lr_parent TYPE REF TO zif_abapgit_ajson_types=>ty_node.
DATA lr_new_node TYPE REF TO zif_abapgit_ajson_types=>ty_node.
read_only_watchdog( ).
lr_parent = get_item( iv_path ).
IF lr_parent IS INITIAL.
zcx_abapgit_ajson_error=>raise( |Path [{ iv_path }] does not exist| ).
ENDIF.
IF lr_parent->type <> zif_abapgit_ajson_types=>node_type-array.
zcx_abapgit_ajson_error=>raise( |Path [{ iv_path }] is not array| ).
ENDIF.
DATA lt_new_nodes TYPE zif_abapgit_ajson_types=>ty_nodes_tt.
DATA ls_new_path TYPE zif_abapgit_ajson_types=>ty_path_name.
DATA lv_new_index TYPE i.
lv_new_index = lr_parent->children + 1.
ls_new_path-path = lcl_utils=>normalize_path( iv_path ).
ls_new_path-name = |{ lv_new_index }|.
lt_new_nodes = lcl_abap_to_json=>convert(
is_opts = ms_opts
iv_data = iv_val
is_prefix = ls_new_path ).
READ TABLE lt_new_nodes INDEX 1 REFERENCE INTO lr_new_node. " assume first record is the array item - not ideal !
ASSERT sy-subrc = 0.
lr_new_node->index = lv_new_index.
" update data
lr_parent->children = lv_new_index.
INSERT LINES OF lt_new_nodes INTO TABLE mt_json_tree.
ri_json = me.
ENDMETHOD.
METHOD zif_abapgit_ajson~set.
DATA ls_split_path TYPE zif_abapgit_ajson_types=>ty_path_name.
DATA lr_parent TYPE REF TO zif_abapgit_ajson_types=>ty_node.
DATA ls_deleted_node TYPE zif_abapgit_ajson_types=>ty_node.
DATA lv_item_order TYPE zif_abapgit_ajson_types=>ty_node-order.
read_only_watchdog( ).
ri_json = me.
IF iv_val IS INITIAL AND iv_ignore_empty = abap_true AND iv_node_type IS INITIAL.
RETURN. " nothing to assign
ENDIF.
IF iv_node_type IS NOT INITIAL
AND iv_node_type <> zif_abapgit_ajson_types=>node_type-boolean AND iv_node_type <> zif_abapgit_ajson_types=>node_type-null
AND iv_node_type <> zif_abapgit_ajson_types=>node_type-number AND iv_node_type <> zif_abapgit_ajson_types=>node_type-string.
zcx_abapgit_ajson_error=>raise( |Unexpected type { iv_node_type }| ).
ENDIF.
ls_split_path = lcl_utils=>split_path( iv_path ).
IF ls_split_path IS INITIAL. " Assign root, exceptional processing
IF iv_node_type IS NOT INITIAL.
mt_json_tree = lcl_abap_to_json=>insert_with_type(
is_opts = ms_opts
iv_data = iv_val
iv_type = iv_node_type
is_prefix = ls_split_path
ii_custom_mapping = mi_custom_mapping ).
ELSE.
mt_json_tree = lcl_abap_to_json=>convert(
is_opts = ms_opts
iv_data = iv_val
is_prefix = ls_split_path
ii_custom_mapping = mi_custom_mapping ).
ENDIF.
RETURN.
ENDIF.
" Ensure whole path exists
lr_parent = prove_path_exists( ls_split_path-path ).
ASSERT lr_parent IS NOT INITIAL.
" delete if exists with subtree
ls_deleted_node = delete_subtree(
ir_parent = lr_parent
iv_path = ls_split_path-path
iv_name = ls_split_path-name ).
lv_item_order = ls_deleted_node-order.
" convert to json
DATA lt_new_nodes TYPE zif_abapgit_ajson_types=>ty_nodes_tt.
DATA lv_array_index TYPE i.
IF lr_parent->type = zif_abapgit_ajson_types=>node_type-array.
lv_array_index = lcl_utils=>validate_array_index(
iv_path = ls_split_path-path
iv_index = ls_split_path-name ).
ELSEIF lr_parent->type = zif_abapgit_ajson_types=>node_type-object
AND lv_item_order = 0 AND ms_opts-keep_item_order = abap_true.
lv_item_order = lr_parent->children + 1.
ENDIF.
IF iv_node_type IS NOT INITIAL.
lt_new_nodes = lcl_abap_to_json=>insert_with_type(
is_opts = ms_opts
iv_item_order = lv_item_order
iv_data = iv_val
iv_type = iv_node_type
iv_array_index = lv_array_index
is_prefix = ls_split_path
ii_custom_mapping = mi_custom_mapping ).
ELSE.
lt_new_nodes = lcl_abap_to_json=>convert(
is_opts = ms_opts
iv_item_order = lv_item_order
iv_data = iv_val
iv_array_index = lv_array_index
is_prefix = ls_split_path
ii_custom_mapping = mi_custom_mapping ).
ENDIF.
" update nodes
IF lines( lt_new_nodes ) > 0.
lr_parent->children = lr_parent->children + 1.
INSERT LINES OF lt_new_nodes INTO TABLE mt_json_tree.
ENDIF.
ENDMETHOD.
METHOD zif_abapgit_ajson~setx.
DATA lv_path TYPE string.
DATA lv_val TYPE string.
DATA lv_int TYPE i.
DATA lv_dec TYPE decfloat34.
DATA lv_last TYPE i.
IF iv_param IS INITIAL.
ri_json = me.
RETURN.
ENDIF.
SPLIT iv_param AT ':' INTO lv_path lv_val.
CONDENSE lv_path.
CONDENSE lv_val.
IF lv_val IS INITIAL.
ri_json = me.
RETURN. " Hmm ? or empty string ? or null ?
ENDIF.
IF go_float_regex IS NOT BOUND.
CREATE OBJECT go_float_regex EXPORTING pattern = '^([1-9][0-9]*|0)\.[0-9]+$'.
" expects fractional, because ints are detected separately
ENDIF.
IF lv_val = 'null'.
zif_abapgit_ajson~set_null( lv_path ).
ELSEIF lv_val = 'true'.
zif_abapgit_ajson~set_boolean(
iv_path = lv_path
iv_val = abap_true ).
ELSEIF lv_val = 'false'.
zif_abapgit_ajson~set_boolean(
iv_path = lv_path
iv_val = abap_false ).
ELSEIF lv_val CO '0123456789'.
lv_int = lv_val.
zif_abapgit_ajson~set_integer(
iv_path = lv_path
iv_val = lv_int ).
ELSEIF lv_val CO '0123456789.' AND go_float_regex->create_matcher( text = lv_val )->match( ) = abap_true.
lv_dec = lv_val.
zif_abapgit_ajson~set(
iv_path = lv_path
iv_val = lv_dec ).
ELSEIF lv_val+0(1) = '{' OR lv_val+0(1) = '['.
"Expect object/array, but no further checks, parser will catch errors
zif_abapgit_ajson~set(
iv_path = lv_path
iv_val = parse(
iv_json = lv_val
iv_keep_item_order = ms_opts-keep_item_order ) ).
ELSE. " string
lv_last = strlen( lv_val ) - 1.
IF lv_val+0(1) = '"' AND lv_val+lv_last(1) = '"'.
lv_val = substring(
val = lv_val
off = 1
len = lv_last - 1 ).
ENDIF.
zif_abapgit_ajson~set_string(
iv_path = lv_path
iv_val = lv_val ).
ENDIF.
ri_json = me.
ENDMETHOD.
METHOD zif_abapgit_ajson~set_boolean.
ri_json = me.
DATA lv_bool TYPE abap_bool.
lv_bool = boolc( iv_val IS NOT INITIAL ).
zif_abapgit_ajson~set(
iv_ignore_empty = abap_false
iv_path = iv_path
iv_val = lv_bool ).
ENDMETHOD.
METHOD zif_abapgit_ajson~set_date.
ri_json = me.
DATA lv_val TYPE string.
lv_val = lcl_abap_to_json=>format_date( iv_val ).
zif_abapgit_ajson~set(
iv_ignore_empty = abap_false
iv_path = iv_path
iv_val = lv_val ).
ENDMETHOD.
METHOD zif_abapgit_ajson~set_integer.
ri_json = me.
zif_abapgit_ajson~set(
iv_ignore_empty = abap_false
iv_path = iv_path
iv_val = iv_val ).
ENDMETHOD.
METHOD zif_abapgit_ajson~set_null.
ri_json = me.
DATA lv_null_ref TYPE REF TO data.
zif_abapgit_ajson~set(
iv_ignore_empty = abap_false
iv_path = iv_path
iv_val = lv_null_ref ).
ENDMETHOD.
METHOD zif_abapgit_ajson~set_string.
ri_json = me.
DATA lv_val TYPE string.
lv_val = iv_val.
zif_abapgit_ajson~set(
iv_ignore_empty = abap_false
iv_path = iv_path
iv_val = lv_val ).
ENDMETHOD.
METHOD zif_abapgit_ajson~set_timestamp.
ri_json = me.
DATA lv_timestamp_iso TYPE string.
lv_timestamp_iso = lcl_abap_to_json=>format_timestamp( iv_val ).
zif_abapgit_ajson~set(
iv_ignore_empty = abap_false
iv_path = iv_path
iv_val = lv_timestamp_iso ).
ENDMETHOD.
METHOD zif_abapgit_ajson~slice.
DATA lo_section TYPE REF TO zcl_abapgit_ajson.
DATA ls_item LIKE LINE OF mt_json_tree.
DATA lv_normalized_path TYPE string.
DATA ls_path_parts TYPE zif_abapgit_ajson_types=>ty_path_name.
DATA lv_path_len TYPE i.
DATA lv_path_pattern TYPE string.
CREATE OBJECT lo_section.
lv_normalized_path = lcl_utils=>normalize_path( iv_path ).
lv_path_len = strlen( lv_normalized_path ).
ls_path_parts = lcl_utils=>split_path( lv_normalized_path ).
READ TABLE mt_json_tree INTO ls_item
WITH KEY path = ls_path_parts-path name = ls_path_parts-name.
IF sy-subrc <> 0.
RETURN.
ENDIF.
CLEAR: ls_item-path, ls_item-name, ls_item-order. " this becomes a new root
INSERT ls_item INTO TABLE lo_section->mt_json_tree.
lv_path_pattern = lv_normalized_path && `*`.
LOOP AT mt_json_tree INTO ls_item WHERE path CP lv_path_pattern.
ls_item-path = substring( val = ls_item-path
off = lv_path_len - 1 ). " less closing '/'
INSERT ls_item INTO TABLE lo_section->mt_json_tree.
ENDLOOP.
ri_json = lo_section.
ENDMETHOD.
METHOD zif_abapgit_ajson~stringify.
rv_json = lcl_json_serializer=>stringify(
it_json_tree = mt_json_tree
iv_keep_item_order = ms_opts-keep_item_order
iv_indent = iv_indent ).
ENDMETHOD.
METHOD zif_abapgit_ajson~touch_array.
DATA lr_node TYPE REF TO zif_abapgit_ajson_types=>ty_node.
DATA ls_deleted_node TYPE zif_abapgit_ajson_types=>ty_node.
DATA ls_new_node LIKE LINE OF mt_json_tree.
DATA ls_split_path TYPE zif_abapgit_ajson_types=>ty_path_name.
read_only_watchdog( ).
ls_split_path = lcl_utils=>split_path( iv_path ).
IF ls_split_path IS INITIAL. " Assign root, exceptional processing
ls_new_node-path = ls_split_path-path.
ls_new_node-name = ls_split_path-name.
ls_new_node-type = zif_abapgit_ajson_types=>node_type-array.
INSERT ls_new_node INTO TABLE mt_json_tree.
RETURN.
ENDIF.
IF iv_clear = abap_true.
ls_deleted_node = delete_subtree(
iv_path = ls_split_path-path
iv_name = ls_split_path-name ).
ELSE.
lr_node = get_item( iv_path ).
ENDIF.
IF lr_node IS INITIAL. " Or node was cleared
DATA lr_parent TYPE REF TO zif_abapgit_ajson_types=>ty_node.
lr_parent = prove_path_exists( ls_split_path-path ).
ASSERT lr_parent IS NOT INITIAL.
lr_parent->children = lr_parent->children + 1.
ls_new_node-path = ls_split_path-path.
ls_new_node-name = ls_split_path-name.
ls_new_node-type = zif_abapgit_ajson_types=>node_type-array.
IF ms_opts-keep_item_order = abap_true AND ls_deleted_node IS NOT INITIAL.
ls_new_node-order = ls_deleted_node-order.
ENDIF.
INSERT ls_new_node INTO TABLE mt_json_tree.
ELSEIF lr_node->type <> zif_abapgit_ajson_types=>node_type-array.
zcx_abapgit_ajson_error=>raise( |Path [{ iv_path }] already used and is not array| ).
ENDIF.
ri_json = me.
ENDMETHOD.
METHOD zif_abapgit_ajson~to_abap.
DATA lo_to_abap TYPE REF TO lcl_json_to_abap.
CLEAR ev_container.
CREATE OBJECT lo_to_abap
EXPORTING
iv_corresponding = boolc( iv_corresponding = abap_true OR ms_opts-to_abap_corresponding_only = abap_true )
ii_custom_mapping = mi_custom_mapping.
lo_to_abap->to_abap(
EXPORTING
it_nodes = zif_abapgit_ajson~mt_json_tree
CHANGING
c_container = ev_container ).
ENDMETHOD.
METHOD zif_abapgit_ajson~to_abap_corresponding_only.
ms_opts-to_abap_corresponding_only = iv_enable.
ri_json = me.
ENDMETHOD.
ENDCLASS.