Last active
March 11, 2020 11:11
-
-
Save mydoghasworms/4888a832e28491c3fe47 to your computer and use it in GitHub Desktop.
ABAP JSON parser and mapper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
*&---------------------------------------------------------------------* | |
*& Include ZTEMPJSON_INCL | |
*&---------------------------------------------------------------------* | |
* A clean, reliable and compliant JSON parser and mapper to ABAP data; | |
* the kind your mother would have encouraged you to hang out with. | |
*----------------------------------------------------------------------* | |
* CLASS json_error DEFINITION | |
*----------------------------------------------------------------------* | |
class json_error definition inheriting from cx_static_check. | |
public section. | |
data: offset type i read-only. | |
methods: constructor importing offset type i. | |
endclass. "lcx_json_error DEFINITION | |
*----------------------------------------------------------------------* | |
* CLASS json_error IMPLEMENTATION | |
*----------------------------------------------------------------------* | |
class json_error implementation. | |
method constructor. | |
super->constructor( ). | |
me->offset = offset. | |
endmethod. "constructor | |
endclass. "lcx_json_error IMPLEMENTATION | |
*----------------------------------------------------------------------* | |
* CLASS json_error_unexpected_char DEFINITION | |
*----------------------------------------------------------------------* | |
class json_error_unexpected_char definition inheriting from json_error. | |
endclass. "json_error_unexpected_char DEFINITION | |
*----------------------------------------------------------------------* | |
* CLASS json_error_unexpected_end DEFINITION | |
*----------------------------------------------------------------------* | |
class json_error_unexpected_end definition inheriting from json_error. | |
endclass. "json_error_unexpected_end DEFINITION | |
*----------------------------------------------------------------------* | |
* CLASS json_error_expecting_delimiter DEFINITION | |
*----------------------------------------------------------------------* | |
class json_error_expecting_delimiter definition inheriting from json_error. | |
endclass. "json_error_expecting_delimiter DEFINITION | |
*----------------------------------------------------------------------* | |
* CLASS json_error_expecting_endinput DEFINITION | |
*----------------------------------------------------------------------* | |
class json_error_expecting_endinput definition inheriting from json_error. | |
endclass. "json_error_expecting_endinput DEFINITION | |
*--------------------------------------------------------------------* | |
* JSON DATATYPES | |
*----------------------------------------------------------------------* | |
* CLASS json_value DEFINITION | |
*----------------------------------------------------------------------* | |
class json_value definition. | |
public section. | |
types: json_array type table of ref to json_value. | |
types: begin of json_keyval, | |
key type string, | |
value type ref to json_value, | |
end of json_keyval. | |
types: json_object type hashed table of json_keyval | |
with unique key key. | |
* 'Type' can have the following values: | |
* 'A' - array | |
* 'O' - object | |
* 'S' - string | |
* 'N' - number | |
* 'B' - boolean | |
* '0' - null | |
data: type type char1. | |
methods: | |
get importing key type any | |
returning value(value) type ref to json_value. | |
endclass. "json_value DEFINITION | |
*----------------------------------------------------------------------* | |
* CLASS json_array DEFINITION | |
*----------------------------------------------------------------------* | |
class json_array definition inheriting from json_value. | |
public section. | |
data: value type json_value=>json_array. | |
methods: get redefinition. | |
endclass. "json_array DEFINITION | |
*----------------------------------------------------------------------* | |
* CLASS json_object DEFINITION | |
*----------------------------------------------------------------------* | |
class json_object definition inheriting from json_value. | |
public section. | |
data: value type json_value=>json_object. | |
methods: | |
get redefinition. | |
endclass. "json_object DEFINITION | |
*----------------------------------------------------------------------* | |
* CLASS json_number DEFINITION | |
*----------------------------------------------------------------------* | |
class json_number definition inheriting from json_value. | |
public section. | |
data: value type decfloat34. "numeric. | |
endclass. "json_number DEFINITION | |
*----------------------------------------------------------------------* | |
* CLASS json_null DEFINITION | |
*----------------------------------------------------------------------* | |
* | |
*----------------------------------------------------------------------* | |
class json_null definition inheriting from json_value. | |
public section. | |
data: value type string. "numeric. | |
endclass. "json_number DEFINITION | |
*----------------------------------------------------------------------* | |
* CLASS json_string DEFINITION | |
*----------------------------------------------------------------------* | |
class json_string definition inheriting from json_value. | |
public section. | |
data: value type string. | |
endclass. "json_string DEFINITION | |
*----------------------------------------------------------------------* | |
* CLASS json_boolean DEFINITION | |
*----------------------------------------------------------------------* | |
class json_boolean definition inheriting from json_value. | |
public section. | |
data: value type boole_d. | |
endclass. "json_boolean DEFINITION | |
*----------------------------------------------------------------------* | |
* CLASS json_object IMPLEMENTATION | |
*----------------------------------------------------------------------* | |
class json_object implementation. | |
method get. | |
data: keyval type json_keyval. | |
read table me->value with key key = key | |
into keyval. | |
if sy-subrc = 0. | |
value = keyval-value. | |
endif. | |
endmethod. "get | |
endclass. "json_object IMPLEMENTATION | |
*----------------------------------------------------------------------* | |
* CLASS json_array IMPLEMENTATION | |
*----------------------------------------------------------------------* | |
* | |
*----------------------------------------------------------------------* | |
class json_array implementation. | |
method get. | |
read table me->value into value index key. | |
endmethod. "get | |
endclass. "json_array IMPLEMENTATION | |
*----------------------------------------------------------------------* | |
* CLASS json_document DEFINITION | |
*----------------------------------------------------------------------* | |
class json_document definition create protected. | |
public section. | |
data: json type string read-only. | |
data: root type ref to json_value read-only. | |
data: length type i. | |
constants: num_pattern type string value '[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?'. | |
class-methods | |
parse | |
importing json type string | |
returning value(document) type ref to json_document | |
raising json_error. | |
methods: | |
next_char raising json_error_unexpected_end, | |
skip_whitespace raising json_error_unexpected_end, | |
parse_array | |
returning value(array) type ref to json_array | |
raising json_error, | |
parse_object | |
returning value(object) type ref to json_object | |
raising json_error, | |
parse_value | |
returning value(value) type ref to json_value | |
raising json_error, | |
parse_string | |
returning value(string) type string | |
raising json_error. | |
methods: | |
map_data | |
importing json_value type ref to json_value | |
changing data type any, | |
map_root | |
changing data type any. | |
protected section. | |
data: index type i. | |
data: char(1) type c. | |
methods: | |
constructor | |
importing json type string | |
raising json_error. | |
methods: | |
map_table | |
importing json_value type ref to json_value | |
changing data type any table, | |
map_structure | |
importing json_value type ref to json_value | |
changing data type any, | |
map_scalar | |
importing json_value type ref to json_value | |
changing data type any. | |
endclass. "json_document DEFINITION | |
*----------------------------------------------------------------------* | |
* CLASS json_value IMPLEMENTATION | |
*----------------------------------------------------------------------* | |
class json_value implementation. | |
method get. | |
endmethod. "get | |
endclass. "json_value IMPLEMENTATION | |
*----------------------------------------------------------------------* | |
* CLASS json_document IMPLEMENTATION | |
*----------------------------------------------------------------------* | |
class json_document implementation. | |
method constructor. | |
me->json = json. "Store local copy of JSON string | |
length = strlen( json ). "Determine length of JSON string | |
index = 0. "Start at index 0 | |
char = json+index(1). "Kick off things by reading first char | |
skip_whitespace( ). | |
case char. | |
when '{'. | |
me->root = parse_object( ). | |
when '['. | |
me->root = parse_array( ). | |
when others. | |
* TODO: raise exception; expecting array or object | |
endcase. | |
if index < length. | |
raise exception type json_error_expecting_endinput | |
exporting | |
offset = index. | |
endif. | |
endmethod. "constructor | |
********************************************************************** | |
* MAPPER METHODS | |
********************************************************************** | |
method map_root. | |
* Convenience method to map the root json entity of the document | |
call method map_data | |
exporting | |
json_value = root | |
changing | |
data = data. | |
endmethod. "map_root | |
method map_data. | |
* Entry point for mapping JSON to ABAP data | |
data: descr type ref to cl_abap_typedescr. | |
descr = cl_abap_datadescr=>describe_by_data( data ). | |
if descr->kind = cl_abap_datadescr=>kind_struct. | |
if json_value->type ne 'O'. "No mapping if no type match | |
return. | |
endif. | |
call method map_structure | |
exporting | |
json_value = json_value | |
changing | |
data = data. | |
elseif descr->kind = cl_abap_datadescr=>kind_table. | |
call method map_table | |
exporting | |
json_value = json_value | |
changing | |
data = data. | |
else. | |
call method map_scalar | |
exporting | |
json_value = json_value | |
changing | |
data = data. | |
endif. | |
endmethod. "map_data | |
method map_scalar. | |
data: descr type ref to cl_abap_typedescr. | |
field-symbols: <value> type any. | |
descr = cl_abap_typedescr=>describe_by_data( data ). | |
if descr->type_kind = cl_abap_datadescr=>typekind_oref. | |
try. | |
data ?= json_value. | |
catch cx_sy_move_cast_error. | |
endtry. | |
else. | |
assign json_value->('VALUE') to <value>. | |
data = <value>. | |
endif. | |
endmethod. "map_scalar | |
method map_structure. | |
if json_value->type ne 'O'. "No mapping if no type match | |
return. | |
endif. | |
data: json_object type ref to json_object. | |
json_object ?= json_value. | |
field-symbols: <field> type any. | |
data: descr type ref to cl_abap_typedescr. | |
data: json_comp type ref to json_value. | |
descr = cl_abap_datadescr=>describe_by_data( data ). | |
data: keyval type json_value=>json_keyval. | |
loop at json_object->value into keyval. | |
translate keyval-key to upper case. | |
assign component keyval-key of structure data to <field>. | |
if sy-subrc ne 0. "No corresponding ABAP component, no match | |
continue. | |
endif. | |
descr = cl_abap_datadescr=>describe_by_data( <field> ). | |
if descr->kind = cl_abap_datadescr=>kind_struct. | |
if keyval-value->type ne 'O'. "No mapping if no type match | |
continue. "Next component of object | |
endif. | |
call method map_structure | |
exporting | |
json_value = keyval-value | |
changing | |
data = <field>. | |
elseif descr->kind = cl_abap_datadescr=>kind_table. | |
if keyval-value->type ne 'A'. "No mapping if no type match | |
return. "Next component of object | |
endif. | |
call method map_table | |
exporting | |
json_value = keyval-value | |
changing | |
data = <field>. | |
else. | |
call method map_scalar | |
exporting | |
json_value = keyval-value | |
changing | |
data = <field>. | |
endif. | |
endloop. | |
endmethod. "map_structure | |
method map_table. | |
if json_value->type ne 'A'. | |
return. | |
endif. | |
data: json_array type ref to json_array. | |
json_array ?= json_value. | |
data: json_row type ref to json_value. | |
field-symbols: <row> type any. | |
field-symbols: <stab> type standard table. | |
field-symbols: <ktab> type sorted table. | |
data: row_data type ref to data. | |
data: descr type ref to cl_abap_typedescr. | |
data: rowdesc type ref to cl_abap_typedescr. | |
data: tabdesc type ref to cl_abap_tabledescr. | |
clear data. | |
create data row_data like line of data. | |
assign row_data->* to <row>. | |
tabdesc ?= cl_abap_typedescr=>describe_by_data( data ). | |
rowdesc = cl_abap_typedescr=>describe_by_data( <row> ). | |
if tabdesc->table_kind = cl_abap_tabledescr=>tablekind_hashed or | |
tabdesc->table_kind = cl_abap_tabledescr=>tablekind_sorted. | |
assign data to <ktab>. | |
else. | |
assign data to <stab>. | |
endif. | |
loop at json_array->value into json_row. | |
if rowdesc->kind = cl_abap_typedescr=>kind_struct. | |
call method map_structure | |
exporting | |
json_value = json_row | |
changing | |
data = <row>. | |
elseif rowdesc->kind = cl_abap_typedescr=>kind_table. | |
call method map_table | |
exporting | |
json_value = json_row | |
changing | |
data = <row>. | |
else. | |
call method map_scalar | |
exporting | |
json_value = json_row | |
changing | |
data = <row>. | |
endif. | |
if tabdesc->table_kind = cl_abap_tabledescr=>tablekind_hashed or | |
tabdesc->table_kind = cl_abap_tabledescr=>tablekind_sorted. | |
insert <row> into table <ktab>. | |
else. | |
append <row> to <stab>. | |
endif. | |
endloop. | |
endmethod. "map_table | |
********************************************************************** | |
* PARSE METHODS | |
********************************************************************** | |
method next_char. | |
index = index + 1. | |
if index < length. | |
char = json+index(1). | |
elseif index = length. | |
char = space. | |
elseif index > length. | |
* Unexpected end reached; exception | |
raise exception type json_error_unexpected_end | |
exporting | |
offset = index. | |
endif. | |
endmethod. "next_char | |
method skip_whitespace. | |
while char = ' ' or char = cl_abap_char_utilities=>newline or char = cl_abap_char_utilities=>cr_lf(1). | |
next_char( ). | |
endwhile. | |
endmethod. "skip_whitespace | |
method parse_array. | |
create object array. | |
array->type = 'A'. | |
next_char( ). "Skip past opening bracket | |
while index < length. | |
skip_whitespace( ). | |
append parse_value( ) to array->value. | |
if char = ','. | |
next_char( ). "Skip comma, on to next value | |
" Continue to next value | |
elseif char = ']'. | |
next_char( ). "Skip past closing bracket | |
"Return completed array object | |
return. | |
else. | |
raise exception type json_error_unexpected_char | |
exporting | |
offset = index. | |
endif. | |
endwhile. | |
endmethod. "parse_array | |
method parse_object. | |
data: entry type json_value=>json_keyval. | |
create object object. | |
object->type = 'O'. | |
next_char( ). "Skip past opening brace | |
while index < length. | |
skip_whitespace( ). | |
if char = '"'. | |
entry-key = parse_string( ). | |
else. | |
* TODO: exception: expecting key | |
endif. | |
skip_whitespace( ). | |
if char = ':'. | |
next_char( ). | |
entry-value = parse_value( ). | |
insert entry into table object->value. | |
else. | |
* TODO: Expecting colon | |
endif. | |
skip_whitespace( ). | |
if char = '}'. "End of object reached | |
" Exit | |
next_char( ). "Skip past closing brace | |
return. | |
elseif char = ','. | |
next_char( ). | |
" Continue to next keyval | |
else. | |
raise exception type json_error_unexpected_char | |
exporting | |
offset = index. | |
endif. | |
endwhile. | |
endmethod. "parse_object | |
method parse_value. | |
skip_whitespace( ). | |
case char. | |
when '['. | |
data: array type ref to json_array. | |
array = parse_array( ). | |
value = array. | |
when '{'. | |
data: object type ref to json_object. | |
object = parse_object( ). | |
value = object. | |
when '"'. | |
data: string type ref to json_string. | |
create object string. | |
string->type = 'S'. | |
string->value = parse_string( ). | |
value = string. | |
when others. | |
data: sval type string. | |
* Run to delimiter | |
while index < length. | |
sval = |{ sval }{ char }|. | |
next_char( ). | |
if char = ',' or char = '}' or char = ']' or char = ' ' or char = cl_abap_char_utilities=>newline | |
or char = cl_abap_char_utilities=>cr_lf(1). | |
exit. | |
endif. | |
endwhile. | |
* Determine different scalar types. Must be: true, false, null or number | |
condense sval. | |
field-symbols: <value> type any. | |
if sval = 'true' or sval = 'false'. | |
create object value type json_boolean. | |
assign value->('VALUE') to <value>. | |
value->type = 'B'. | |
if sval = 'true'. | |
<value> = abap_true. | |
else. | |
<value> = abap_false. | |
endif. | |
elseif sval = 'null'. | |
create object value type json_null. | |
value->type = '0'. | |
else. | |
find regex num_pattern in sval. | |
if sy-subrc ne 0. | |
raise exception type json_error_unexpected_char | |
exporting | |
offset = index. | |
endif. | |
create object value type json_number. | |
assign value->('VALUE') to <value>. | |
value->type = 'N'. | |
<value> = sval. | |
endif. | |
endcase. | |
* After parsing array or object, cursor will be on closing bracket or brace, so we want to skip to next char | |
skip_whitespace( ). "Skip to next character, which should be a delimiter of sorts | |
if char ne ',' and char ne '}' and char ne ']'. | |
raise exception type json_error_expecting_delimiter | |
exporting | |
offset = index. | |
endif. | |
endmethod. "parse_value | |
method parse_string. | |
data: pchar(1) type c. "Previous character | |
while index < length. | |
next_char( ). | |
if char = '"' and pchar ne '\'. | |
exit. | |
endif. | |
concatenate string char into string respecting blanks. | |
pchar = char. | |
endwhile. | |
next_char( ). "Skip past closing quote | |
endmethod. "parse_string | |
method parse. | |
create object document | |
exporting | |
json = json. | |
endmethod. "parse | |
endclass. "json_document IMPLEMENTATION |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi
it is work fine