Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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

This comment has been minimized.

Copy link

@heinzd 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

This comment has been minimized.

Copy link
Owner Author

@mydoghasworms mydoghasworms commented Nov 24, 2015

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

This comment has been minimized.

Copy link

@DovFel 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