Skip to content

Instantly share code, notes, and snippets.

@mydoghasworms
Last active March 11, 2020 11:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mydoghasworms/4888a832e28491c3fe47 to your computer and use it in GitHub Desktop.
Save mydoghasworms/4888a832e28491c3fe47 to your computer and use it in GitHub Desktop.
ABAP JSON parser and mapper
*&---------------------------------------------------------------------*
*& 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
@heinzd
Copy link

heinzd commented May 20, 2015

Hi Martin, great work. I tried already your form routine and it works perfectly. But this class version has syntax errors: Line 541, sval = |{ sval }{ char }|.
Do you have an updated version of this code?
Best regards
Heinz

@mydoghasworms
Copy link
Author

Hello Heinz,
Sorry for the late reply (I don't get notification on Gist comments; maybe there is a setting on my profile I need to change?).
Anyway, I do not encounter the specific syntax error you have. Is it because you are on an older ABAP release that does not support string templates?

@DovFel
Copy link

DovFel commented Mar 10, 2020

Hi
it is work fine

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