【学习转载】One more ABAP to JSON Serializer and Deserializer

Why

There are a lot of other implementations of the ABAP to JSON Serializer and Deserializer in SDN, but for different reasons, all implementations I have found were not suitable for my needs. From SAP_BASIS 7.40 there is also a simple transformation available for converting ABAP to JSON and JSON to ABAP. It is the best choice if you need maximal performance and does not care about serialization format, but for proper handling of ABAP types and name pretty-printing, it fits badly. 

So, I have written my own ABAP JSON serializer and ABAP JSON deserializer which has some key differentiates from other implementations.

Below you can find a snippet of the ABAP JSON class I wrote, that you can use as a local class or global renamed.

An original and actual version of the source can be found in class /UI2/CL_JSON delivered with UI2 Add-on (can be applied to SAP_BASIS 700 – 76X). So, you can use this ABAP JSON parser in your standard code mostly on any system. 

What it can

ABAP to JSON

  • Serialize classes, structures, internal tables, class and data references, any kind of elementary types. Complex types, as a table of structures/classes, classes with complex attributes, etc. are also supported and recursively processed.
  • ABAP to JavaScript adopted way of data type serializations:
    • strings, character types to JavaScript string format (no length limitation),
    • ABAP_BOOL / BOOLEAN / XFELD / BOOLE_D to JavaScript Boolean,
    • Built-in TRIBOOL (TRUE/FALSE/UNDEFINED = 'X'/'-'/'') support, for better control of initial values when serializing into JavaScript  Boolean
    • int/floats/numeric/packed to JavaScript Integers/floats,
    • date/time to JavaScript date/time string representation as "2015-03-24" or "15:30:48",
    • timestamp to JavaScript  integer or ISO8601 string
    • structures to JavaScript objects (include types are also supported; aliases => AS are ignored)
    • convert ABAP internal table to JSON, e.g JavaScript arrays or associative arrays (objects)
  • Support of conversion exits on ABAP data serialization
  • Pretty Printing of JavaScript property names: MY_DATA -> myData/SAPAPO/MY_DATA -> sapapoMyData.
  • Condensing of default values: initial values are not rendered into the resulting JSON string
  • Performance is optimized for processing big internal tables with structures

JSON to ABAP

  • Deserialize JSON objects, arrays, and any elementary types into corresponding ABAP structures. Complex objects, with embedded arrays and objects with any level of nesting, are also supported.
  • Convert JSON to an internal table
  • Generic deserialization of JSON objects into reference data types: 
    • as simple data types (integer, boolean, or string into generic data reference (REF TO DATA) -> ABAP type is selected based on JSON type.
    • as dynamically generated complex object (structures, tables, mixed) for initial REF TO DATA fields
    • as typed references for prefilled REF TO DATA fields (you assign a reference to typed empty data object to REF TO DATA field in execution time)
  • Deserialization of unknown JSON structures possible using method GENERATE into on the fly created data types
  • On JSON to ABAP transformation following rules are used:
    • objects parsed into corresponding ABAP structures, classes (only classes with constructors with no obligatory parameters are supported), or internal hash/sorted tables
    • arrays converted to internal tables (complex tables are also supported). 
    • Boolean converted as ABAP_BOOL (‘’ or ‘X’)
    • Date/Time/Timestamps from JSON converted based on the type of corresponding ABAP element
    • integers/floats/strings moved to corresponding fields using ABAP move semantic (strings are un-escaped). There is no limit on the size of deserialized strings, the only restriction is the constraints of receiving data type. Escaped Unicode symbols (\u001F) ins strings are decoded.
    • elementary data types are converted if do not match: JavaScript integer can come into ABAP string or JavaScript string into ABAP integer, etc.
    • Transformation takes into account property naming guidelines for JSON and ABAP so that camelCase names will be copied into the corresponding CAMEL_CASE field if the CAMELCASE field is not found in the ABAP structure. Do not forget to use the same PRETTY_MODE for deserialization, as you have used for serialization.
    • Default field values, specified in reference ABAP variable are preserved, and not overwritten if not found in the JSON object
    • Transformation of JSON structures into ABAP class instances is NOT supported.
  • Support of conversion exits on deserialization

Parser for serialize/deserialize uses single-pass parsing and optimized to provide the best possible performance in ABAP in release independent way. But for time-critical applications, which have kernel version 7.20 and higher, it is recommended to use built-in JSON to ABAP transformations (CALL TRANSFORMATION). If transformation for some reason does not work, please assist the following notes: 1650141 and 1648418.

Usage example

ABAP to JSON usage example

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

DATA: lt_flight TYPE STANDARD TABLE OF sflight,

      lrf_descr TYPE REF TO cl_abap_typedescr,

      lv_json   TYPE string.

 

  

SELECT * FROM sflight INTO TABLE lt_flight.

  

* serialize table lt_flight into JSON, skipping initial fields and converting ABAP field names into camelCase

lv_json = /ui2/cl_json=>serialize( data = lt_flight compress = abap_true pretty_name = /ui2/cl_json=>pretty_mode-camel_case ).

WRITE / lv_json.

 

CLEAR lt_flight.

  

* deserialize JSON string json into internal table lt_flight doing camelCase to ABAP like field name mapping

/ui2/cl_json=>deserialize( EXPORTING json = lv_json pretty_name = /ui2/cl_json=>pretty_mode-camel_case CHANGING data = lt_flight ).

 

* serialize ABAP object into JSON string

lrf_descr = cl_abap_typedescr=>describe_by_data( lt_flight ).

lv_json = /ui2/cl_json=>serialize( lrf_descr ).

WRITE / lv_json.

Output

[{"mandt":"000","carrid":"AA","connid":0017,"fldate":20130515,"price":422.94,"currency":"USD","planetype":"747-400","seatsmax":385,
"seatsocc":375,"paymentsum":192683.30,"seatsmaxB":31,"seatsoccB":31,"seatsmaxF":21,"seatsoccF":19},{"mandt":"000","carrid":"AA",
"connid....
{"ABSOLUTE_NAME":"\\TYPE=%_T00004S00000000O0000012480","ADMIN_TAB":"\\TYPE=%_T00004S00000000O0000012480",
"ADMIN_TAB_LINE":"\\TYPE=%_T00004S00000000O0000012480","DECIMALS":0,"HAS_UNIQUE_KEY":false,"INITIAL_SIZE":0,
"KEY":[{"NAME":"MANDT"},{"NAME":"CARRID"},{"NAME":"CO....

API description

Two static methods are of most interest in common cases: SERIALIZE and DESERIALIZE. The rest of the public methods are done public only for reuse purposes if you would like to build/extend your own serialization/deserialization code. 

SERIALIZE: Serialize ABAP object into JSON

  • > DATA (any) - any ABAP object/structure/table/element to be serialized
  • > COMPRESS (bool, default=false) - tells serializer to skip empty elements/objects during serialization. So, all for which IS INITIAL = TRUE. 
  • > NAME (string, optional) - optional name of the serialized object. Will '"name" : {...}' instead of ' {...} ' if supplied. 
  • > PRETTY_NAME (enum, optional)  - mode, controlling how ABAP field names transformed in JSON attribute names. See the description below.  
  • > TYPE_DESCR (ref to CL_ABAP_TYPEDESCR, optional) - if you know object type already - pass it to improve performance. 
  • > ASSOC_ARRAYS (bool, default = false) - controls how to serialize hash or sorted tables with unique keys. See below for details.
  • > ASSOC_ARRAYS_OPT (bool, default = false) - when set, serializer will optimize rendering of name-value associated arrays (hash maps) in JSON
  • > TS_AS_ISO8601 (bool, default = false) - says serializer to output timestamps using ISO8601 format.
  • > NUMC_AS_STRING (bool, default = false) - Controls the way how NUMC fields are serialized. If set to ABAP_TRUE, NUMC fields serialized not as integers, but as strings, with all leading zeros. Deserialization works compatible with both ways of NUMC serialized data.
  • > CONVERSION_EXITS (bool, default = false) - use DDIC conversion exits on serialize of values (performance lost!)
  • < R_JSON - output JSON string.

DESERIALIZE: Deserialize ABAP object from JSON string

  • > JSON (string) - input JSON object string to deserialize
  • > PRETTY_NAME (enum, optional) - mode, controlling how JSON field names mapped to ABAP component names. See the description below.  
  • > ASSOC_ARRAYS (bool, default = false) -  controls how to deserialize JSON objects into hash or sorted tables with unique keys. See below for details.
  • > ASSOC_ARRAYS_OPT (bool, default = false) - when set, the deserializer will take into account the optimized rendering of associated arrays (properties) in JSON. 
  • > TS_AS_ISO8601 (bool, default = false) - says deserializer to read timestamps from strings into timestamps fields using ISO 8601 format.
  • > CONVERSION_EXITS (bool, default = false) - use DDIC conversion exits on deserialize of values (performance lost!)
  • <> DATA (any) - ABAP object/structure/table/element to be filled from JSON string. If the ABAP structure contains more fields than in the JSON object, the content of unmatched fields is preserved.

GENERATE: Generates ABAP object from JSON

  • > JSON (string) - input JSON object string to deserialize
  • > PRETTY_NAME (enum, optional) - mode, controlling how JSON field names mapped to ABAP component names. See the description below.  
  • < RR_DATA (REF TO DATA) - reference to ABAP structure/table dynamically generated from JSON string.

In addition to the explained methods, there are two options, that need a wider explanation:

PRETTY_NAME: enumeration of modes, defined as constant /UI2/CL_JSON=>pretty_name.

  • NONE - ABAP component names serialized as is (UPPERCASE).
  • LOW_CASE - ABAP component names serialized in low case 
  • CAMEL_CASE - ABAP component types serialized in CamelCase where symbol "_" is treated as word separator (and removed from the resulting name). 
  • EXTENDED - works the same way as CAMEL_CASE but also, has extended logic for encoding special characters, as: ".", "@", "~", etc. Shall be used if you need JSON names with characters not allowed for ABAP data component names. Do not use it, if you do not have special characters in JSON names - the performance would be slower in comparison with CAMEL_CASE mode. Example: ABAP name '__A__SCHEMA' translates in JSON name '@schema'
    Encoding rules (ABAP name → JSON name):
    • '__E__' → '!'
    • '__N__' → '#'
    • '__D__' → '$'
    • '__P__' → '%'
    • '__M__' → '&'
    • '__S__' → '*'
    • '__H__' → '-'
    • '__T__' → '~'
    • '__L__' → '/'
    • '__C__' → ':'
    • '__V__' → '|'
    • '__A__' → '@'
    • '__O__' or '___' → '.'

NONE and LOW_CASE work the same way for DESERIALIZE.

ASSOC_ARRAYS :

This option controls the way how hashed or sorted tables with unique keys serialized/deserialized. Normally, ABAP internal tables are serialized into JSON arrays, but in some cases, you will like to serialize them as associative arrays (JSON object) where every row of the table shall be reflected as a separated property of JSON object. This can be achieved by setting the ASSOC_ARRAYS parameter to TRUE. If set, the serializer checks for sorted/hashed tables with a UNIQUE key(s) and serialize them as an object. The JSON property name, reflecting row, constructed from values of fields, used in key separated by constant MC_KEY_SEPARATOR = '-'. If the table has only one field marked as key, the value of this single field becomes a property name and REMOVED from the associated object (to eliminate redundancy). If TABLE_LINE is used as a unique key, all values of all fields construct key property names (separated by MC_KEY_SEPARATOR). During deserialization, logic works vice versa: if ASSOC_ARRAYS set to TRUE, and JSON object matches internal hash or sorted table with the unique key, the object is transformed into the table, where every object property reflected in a separated table row. If the ABAP table has only one key field, the property name is transformed into a value of this key field.

ASSOC_ARRAYS_OPT:

By default, when dumping hash/sorted tables with a unique key into JSON, the serializer will write key field as the property name, and the rest of the fields will write object value of properties:

Dumping of hash tables from ABAP to JSON

TYPES: BEGIN OF ts_record,

        key TYPE string,

        value TYPE string,

       END OF ts_record.

  

DATA: lt_act TYPE SORTED TABLE OF ts_record WITH UNIQUE KEY key.

lv_json = /ui2/cl_json=>serialize( data = lt_exp assoc_arrays = abap_true ).

Output JSON

{

    "KEY1": {

        "value""VALUE1"

    },

    "KEY2": {

        "value""VALUE2"

    }

}

But if you will use the assoc_arrays_opt flag during serialization, the serializer will try to omit unnecessary object nesting on dumping of simple, name/value tables, containing only one key field and one value field:

Dumping of hash tables from ABAP to JSON

lv_json = /ui2/cl_json=>serialize( data = lt_exp assoc_arrays = abap_true assoc_arrays_opt = abap_true ).

Output JSON

{

    "KEY1""VALUE1",

    "KEY2""VALUE2"

}

For deserialization, the flag is used to tell the deserializer that value shall be placed in a non-key field of the structure.

Supported SAP_BASIS releases

The code was tested from SAP_BASIS 7.00 and higher, but I do not see the reasons why it cannot be downported on lower releases too. But if you plan to use it on SAP_BASIS 7.02 and higher (and do not need property name pretty-printing) better consider the standard solution for ABAP, using CALL TRANSFORMATION. It shall be definitely faster, while implemented in the kernel. See the blog of Horst Keller for details. Maybe the best will be, if you need support in lower SAP_BASIS releases as well as in 7.02 and higher, to modified provided a class in a way to generate the same JSON format as standard ABAP CALL TRANSFORMATION for JSON does and redirect flow to home-made code or built-in ABAP transformation depending on SAP_BASIS release.

Further optimizations

  • Be aware, that usage of flag conversion_exits may significantly decrease performance - use only in cases, when you are sure that you need it.
  • Escaping property values can be expensive. To optimize performance, in this case, you can replace escapement code with some kernel implemented function (from cl_http_utility class for example), instead of explicit REPLACE ALL OCCURRENCES calls.
  • Unescaping can influence deserialization performance even worse, depending on the fact if your JSON has encoded \n\r\t\f\b\x. So, avoid the usage of them if you can. 
  • It is possible to significantly increase performance for serialization/deserialization by dropping the support of releases below 7.40. That can be realized by moving base parsing from ABAP to kernel implemented classes cl_sxml_string_writer and cl_sxml_string_reader.

Remarks

Due to optimization reasons, some methods were converted to macros, to reduce overhead for calling methods for data type serialization. If performance in your case is not critical, and you prefer clean/debuggable code you can replace macro calls with corresponding methods.

Related pages

The /UI2/CL_JSON code

Below you can find the code itself, which you can use (corresponds to the state of PL14).

If you want to use the class globally, I suggest creating a proxy class, in your own namespace, with a reduced interface (serialize/deserialize only) and call local copy (local class of it) of /UI2/CL_JSON. Then you can easily update to the new version of /UI2/CL_JSON from SDN or call UI Addon implementation if it is installed.

ZCL_JSON code 展开源码

Custom ABAP to JSON, JSON to ABAP name mapping

By default, you control the way JSON names are formatted/mapped to ABAP names by selecting proper pretty_mode as a parameter for SERIALIZE/DESERIALIZE/GENERATE method. But in some cases, the standard, hard-coded formatting, is not enough. For example, if you need special rules for name formatting (for using special characters) or because JSON attribute name is too long and you can not map it to ABAP name (which has 30 characters length limit). 

The recommended way for custom mapping was an extension of the /UI2/CL_JSON class and redefining methods PRETTY_NAME or PRETTY_NAME_EX, but since note 2526405 there is an easier way, without the need in own class. If you have a static list of field mappings from ABAP to JSON you can pass the name mapping table as a parameter for the constructor/serialize/deserialize and control the way JSON names are formatted/mapped to ABAP names. 

ABAP to JSON name mapping

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

TYPES:

  BEGIN OF tp_s_data,

    sschema             TYPE string,

    odatacontext        TYPE string,

    shortened_abap_name TYPE string,

    standard            TYPE string,

  END OF tp_s_data.

 

DATA: ls_exp      TYPE tp_s_data,

      lt_mapping  TYPE /ui2/cl_json=>name_mappings,

      lv_json     TYPE /ui2/cl_json=>json.

 

lt_mapping = VALUE #( ( abap = `SSCHEMA` json = `$schema` )

                      ( abap = `ODATACONTEXT` json = `@odata.context` )

                      ( abap = `SHORTENED_ABAP_NAME` json = `VeeeeryyyyyLooooongJSONAttrbuuuuuuuuuteeeeeeeeeee` ) ).

 

lv_json = /ui2/cl_json=>serialize( data = ls_exp name_mappings = lt_mapping ).

Custom formatting of values for serialization of ABAP into JSON

In some cases, you need to have custom formatting for your ABAP data, when serializing it into JSON. Or another use case, you have some custom, DDIC defined data types, that are not automatically recognized by standard code, and therefore no appropriate formatting is applied (for example custom boolean or timestamp type). 

In this case, you have the following options:

  1. Extend the class and overwrite the method DUMP_TYPE. See an example in section "/UI2/CL_JSON extension".
  2. Add conversion exits for your custom type and apply formatting as part of the conversion exit.
  3. Create an alternative structure, with your custom types replaced by supported types, only for serialization, and do the move of data before the serialization. 

Serialization/deserialization of hierarchical/recursive data

Handling the recursive data structure in ABAP is not very trivial. And it is not very trivial to serialize and deserialize it either.
If you would like to model your hierarchical data (tree-like) as ABAP structures, the only allowed way will be to do it like in the example below, where you use references to generic data:

Modeling of recursive data types in ABAP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

TYPES:

  BEGIN OF ts_node,

    id        TYPE i,

    children  TYPE STANDARD TABLE OF REF TO data WITH DEFAULT KEY,

  END OF ts_node.

 

DATA: lv_exp    TYPE string,

      lv_act    TYPE string,

      ls_data   TYPE ts_node,

      lr_data   LIKE REF TO ls_data.

 

ls_data-id = 1.

 

CREATE DATA lr_data.

lr_data->id = 2.

APPEND lr_data TO ls_data-children.

Such a way more or less straightforward and will work, but leads to losing type information for data persisted in children table. That will mean that you will need to cast data when you access it. In addition to that, it blocks you from being able to deserialize such data from JSON, while the parser will not be able to deduce the type of data that needs to be created in the children's table. But serialization will work fine:

Serialization of recursive ABAP structures

1

2

3

lv_exp = '{"ID":1,"CHILDREN":[{"ID":2,"CHILDREN":[]}]}'.

lv_act = /ui2/cl_json=>serialize( data = ls_data ).

cl_aunit_assert=>assert_equals( act = lv_act exp = lv_exp msg = 'Serialization of recursive data structure fails' ).

The better way to model hierarchical data in ABAP is with help of objects, while objects are always processed as references and ABAP allow you to create nested data structures, referring to objects of the same type:

Modeling of recursive data in ABAP using objects

1

2

3

4

5

CLASS lcl_test DEFINITION FINAL.

  PUBLIC SECTION.

    DATA: id TYPE i.

    DATA: children TYPE STANDARD TABLE OF REF TO lcl_test.

ENDCLASS.                    "lcl_test DEFINITION

In that manner, you can process data in the same way as with ABAP structures but using typed access and serialization/deserialization of data in JSON works fine while types can be deduced on 

Serialization/deserialization of recursive objects in ABAP

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

DATA: lo_act    TYPE REF TO lcl_test,

      lo_exp    TYPE REF TO lcl_test,

      lv_json   TYPE string,

      lo_child  TYPE REF TO lcl_test.

 

CREATE OBJECT lo_exp.

 

lo_exp ->id = 1.

 

CREATE OBJECT lo_child.

lo_child->id = 2.

APPEND lo_child TO lo_exp->children.

 

lv_json = /ui2/cl_json=>serialize( data = lo_exp ).

ui2/cl_json=>deserialize( EXPORTING json = lv_json CHANGING data =  lo_act ).

Remark: There are some constraints for data design that exist regarding the deserialization of objects:

  • You cannot use constructors with obligatory parameters
  • References to interfaces will be not deserialized

Serializing of protected and private attributes

If you do the serialization from outside of your class, you can access only the public attributes of that class. To serialize all types of attributes (private+protected) you need to allow /ui2/cl_json access to them. This can be done by defining /ui2/cl_json as a friend of your class. In this way, you do not disrupt your encapsulation for other classes but enable /ui2/cl_json to access all data of your class.

If you do not own a class you want to serialize, you probably can inherit it from your class and add friends there. In this case, you can access at least protected attributes.

Partial serialization/deserialization

When it is needed:

  • You deserialize JSON to ABAP but would like some known parts to be deserialized as JSON string, while you do not know nesting JSON structure.
  • You deserialize a collection (array/associative array) which has objects with heterogeneous structure (for example the same field has different type depending on object type). Using partial deserialization, you can restore such a type as JSON string in ABAP and apply later additional deserialization based on the object type.  
  • You serialize ABAP to JSON and have some ready JSON pieces (strings) which you would like to mix in. 

The solution /UI2/CL_JSON has for this type /UI2/CL_JSON=>JSON (alias for built-in type string). ABAP fields using declared with this type will be serialized/deserialized as JSON pieces. Be aware that during serialization from ABAP to JSON, the content of such JSON piece is not validated for correctness, so if you pass an invalid JSON block, it may destroy the whole resulting JSON string at the end.

Below you can find examples for partial serialization/deserialization.

Serialization:

Partial serialization of ABAP to JSON 展开源码

Results in:

JSON Output 展开源码

Deserialization:

Partial deserialization of JSON into ABAP 展开源码

Results in the following ABAP data:

ABAP Output (variant 1) 展开源码

ABAP Output (variant 2) 展开源码

/UI2/CL_JSON extension

If standard class functionality does not fit your requirements there are two ways of how you can adapt it to your needs:

  • Use a local copy of the class /UI2/CL_JSON and modify logic directly, by the change of original code.
  • Inherit from class /UI2/CL_JSON and override methods where another logic is required. 

The advantage of the first approach that you are completely free in what you may change and have full control of the class lifecycle. The disadvantage, you will probably need to merge your changes with /UI2/CL_JSON updates. 

For the second approach you can use /UI2/CL_JSON directly (prerequisite is the latest version of note 2330592), do not need to care about the merge, but can override only some methods. The methods are:

IS_COMPRESSIBLE – called to check, if the given type output may be suppressed during ABAP to JSON serialization when a value is initial. 

  • > TYPE_DESCR (ref to CL_ABAP_TYPEDESCR) – value type
  • < RV_COMPRESS (bool) – compress initial value

The default implementation of the method allows compressing any initial value.

PRETTY_NAME – called to format ABAP field name written to JSON or deserialized from JSON to ABAP field, when the pretty_name parameter of SERIALIZE/DESERIALIZE method equal to PRETTY_MODE-CAMEL_CASE.

  • > IN (CSEQUENCE) – Field name to pretty print.
  • < OUT (STRING) – Pretty printed field name

The default implementation applies camelCase formatting, based on usage of the “_” symbol. To output, the “_” symbol, use the double “__” symbol in the field name.

PRETTY_NAME_EX – called to format ABAP field name written to JSON or deserialized from JSON to ABAP field, when the pretty_name parameter of SERIALIZE/DESERIALIZE method equal to PRETTY_MODE-EXTENDED.

  • > IN (CSEQUENCE) – Field name to pretty print.
  • < OUT (STRING) – Pretty printed field name

The default implementation does the same as PRETTY_NAME, plus converting special characters "!#$%&*-~/:|@.". 

DUMP_INT - called for recursive serialization of complex ABAP data objects (structure, class, table) into JSON string  

  • > DATA (DATA) – Any data to serialize.
  • > TYPE_DESCR (ref to CL_ABAP_TYPEDESCR, optional) – Type of data provided
  • < R_JSON (JSON) – serialized JSON value

DUMP_TYPE - called for serialization of elementary ABAP data type (string, boolean, timestamp, etc) into the JSON attribute value. Overwrite it if you, for example, want to apply data output data conversion of currency rounding  

  • > DATA (DATA) – Any data to serialize
  • > TYPE_DESCR (ref to CL_ABAP_TYPEDESCR) – Type of data provided
  • < R_JSON (JSON) – serialized JSON value

RESTORE - called for deserializing JSON objects into ABAP structures

  • > JSON (JSON) – JSON string to deserialize
  •  LENGTH (I) – Length of the JSON string
  • > TYPE_DESCR (ref to CL_ABAP_TYPEDESCR, optional) – Type of changing data provided
  • > FIELD_CACHE (type T_T_FIELD_CACHE, optional) – Cache of ABAP data fields with type information
  • <> DATA (type DATA, optional) – ABAP data object to fill
  • <> OFFSET (I) – parsing start point in JSON string

RESTORE_TYPE - called to deserializing simple JSON attributes and JSON arrays

  • > JSON (JSON) – JSON string to deserialize
  •  LENGTH (I) – Length of the JSON string
  • > TYPE_DESCR (ref to CL_ABAP_TYPEDESCR, optional) – Type of changing data provided
  • > FIELD_CACHE (type T_T_FIELD_CACHE, optional) – Cache of ABAP data fields with type information
  • <> DATA (type DATA, optional) – ABAP data object to fill
  • <> OFFSET (I) – parsing start point in JSON string

CLASS_CONSTRUCTOR - used to initialize static variables. You can not overwrite it, but you can implement your own class constructor that adapts default globals. For example, adds the additional boolean types to be recognized during serialization/deserialization. 

SERIALIZE/DESERIALIZE - these methods are static therefore cannot be redefined. Methods are helpers for a consumption code, hiding the construction of the class instance and further *_INT calls. So, if you would like to use something similar, in your custom class, you need to copy mentioned methods to new ones e,g *_EX, and overwrite there /UI2/CL_JSON type to your custom class name. And use these methods instead of standard.

Extension using inheritance:

Extension of /UI2/CL_JSON 展开源码

Results in the following JSON:

Serialization with custom /UI2/CL_JSON 展开源码

Deserialization of an untyped (unknown) JSON object

If you need to deserialize a JSON object with an unknown structure, or you do not have a passing data type on the ABAP side or the data type of the resulting object may vary, you can generate an ABAP object on the fly, using the corresponding GENERATE method. The method has some limitations comparing to standard deserialization like:

  • all fields are generated as a reference (even elementary types)
  • you can not control how deserialized arrays or timestamps
  • you can not access components of generated structure statically (while the structure is unknown at compile time) and need to use dynamic access

The simplest example, with straightforward access:

Generating of ABAP Data using /UI2/CL_JSON

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

DATA: lv_json TYPE /ui2/cl_json=>json,

      lr_data TYPE REF TO data.

 

FIELD-SYMBOLS:

  <data>   TYPE data,

  <struct> TYPE any,

  <field>  TYPE any.

 

lv_json = `{"name":"Key1","properties":{"field1":"Value1","field2":"Value2"}}`.

lr_data = /ui2/cl_json=>generate( json = lv_json ).

 

" OK, generated, now let us access somete field :(

IF lr_data IS BOUND.

  ASSIGN lr_data->* TO <data>.

  ASSIGN COMPONENT `PROPERTIES` OF STRUCTURE <data> TO <field>.

  IF <field> IS ASSIGNED.

    lr_data = <field>.

    ASSIGN lr_data->* TO <data>.

    ASSIGN COMPONENT `FIELD1` OF STRUCTURE <data> TO <field>.

    IF <field> IS ASSIGNED.

      lr_data = <field>.

      ASSIGN lr_data->* TO <data>.

      WRITE: <data>. " We got it -> Value1

    ENDIF.

  ENDIF.

ENDIF.

A nice alternative, using dynamic data accessor helper class

Access generated ABAP data object using dynamic data accessor helper

1

2

3

4

5

6

7

8

9

DATA: lv_json TYPE /ui2/cl_json=>json,

      lr_data TYPE REF TO data,

      lv_val  TYPE string.

 

lv_json = `{"name":"Key1","properties":{"field1":"Value1","field2":"Value2"}}`.

lr_data = /ui2/cl_json=>generate( json = lv_json ).

 

/ui2/cl_data_access=>create( ir_data = lr_data iv_component = `properties-field1`)->value( IMPORTING ev_data = lv_val ).

WRITE: lv_val.

Implicit generation of ABAP objects on deserialization

In addition to the explicit generation of the ABAP data objects from JSON string, the deserializer supports an implicit way of generation, during DESERIALIZE(INT) call. To trigger generation, your output data structure shall contain a field with the type REF TO DATA, and the name of the field shall match the JSON attribute (pretty name rules are considered). Depending on the value of the field, the behavior may differ:

  • The value is not bound (initial): deserialize will use generation rules when creating corresponding data types of the referenced value
  • The value is bound (but may be empty): the deserializer will create a new referenced value based on the referenced type.

Example of implicit generation of ABAP data from JSON string 展开源码

JSON/ABAP serialization/deserialization with runtime type information

Automatic deserialization of the JSON into appropriate ABAP structure is not supported. The default implementation assumes that you need to know the target data structure (or at least partial structure, it will also work) to deserialize JSON in ABAP and then work with typed data. 

But if for some reason one needs the ability to deserialize JSON in source ABAP structure in a generic way, he can extend both serialize/deserialize methods and wrap outputs/inputs of /UI2/CL_JSON data by technical metadata describing source ABAP structure and use this information during deserialization (or use GENERATE method). Of course, you need to ensure that the source ABAP data type is known in the deserialization scope (global and local types are "visible").

See the example below:

Serialization and deserialization with runtime type information 展开源码

Exception Handling in /UI2/CL_JSON

By default, /UI2/CL_JSON tries to hide from consumer thrown exceptions (that may happen during deserialization) catching them at all levels. In some cases, it will result in missing attributes, in other cases, when an error was critical and the parser can not restore, you will get an empty object back. The main TRY/CATCH block, not letting exceptions out is in DESERIALIZE method.

If you want to get a reporting in case of error, you shall use instance method DESERIALIZE_INT which may fire CX_SY_MOVE_CAST_ERROR. The reporting is rather limited - all errors translated into CX_SY_MOVE_CAST_ERROR and no additional information available.

JSON to ABAP transformation with the use of CALL TRANSFORMATION

Below is a small example of CALL TRANSFORMATION usage to produce JSON from ABAP structures. Do not ask me for details - I do not know them.   Was just a small test of me.

CALL TRANSFORMATION for JSON 展开源码

Version History

Note 2944398 - PL15

/UI2/CL_JSON

  • Fixed. Generating of ABAP structures for JSON attributes which include special characters like "/\:;~.,-+=><|()[]{}@+*?!&$#%^'§`" fails.
  • Fixed. Edm.Guid with the mixed or lower case is not recognized.
  • Fixed. Iterating and data assignments for the generated data object (REF TO DATA), produced by GENERATE and DESERIALIZE methods fail in ABAP Cloud. 

/UI2/CL_DATA_ACCESS

  • Fixed. Index access to generated tables in LOOP construction fails.

Note 2904870 - PL14

/UI2/CL_JSON

  • Fixed. Unescaping of strings with a single Unicode entity (e.g "\uXXXX") does not work 
  • New. More robust logic for handling invalid JSON (e.g cases with extra "," without further element  { "a": 1, } )

Note 2870163 - PL13

/UI2/CL_JSON

  • Fixed. Conversion exists does not work when data located inside of internal tables.
  • Fixed. TIMESTAMPL subsecond values are truncated when deserializing from Edm.DateTime.

Note 2798102 - PL12

/UI2/CL_JSON

  • New. DESERIALIZE and GENERATE methods supporting decoding of Unicode symbols (\u001F) 
  • Fixed. Invalid JSON causing <STING_OFFSET_TOO_LARGE> exception and dump.

/UI2/CL_DATA_ACCESS

  • Fixed. Access to fields with special characters in the name (e.g "/BIC/YEAR") fails.

Note 2786259 - PL11

/UI2/CL_JSON

  • Optimized. Performance lost, introduced in PL10 (note 2763854) when unescaping special characters (\r\n\t\")
  • Fixed. Short dump, with <STRING_OFFSET_TOO_LARGE> when running GENERATE method with empty or invalid input

/UI2/CL_DATA_ACCESS

  • Fixed. Short dump, when accessing elements of null array

Note 2763854 - PL10

  • Fixed: Deserialization and generation of the ABAP data from JSON strings with Unicode characters fail
  • Fixed: Unescaping of \\t and \\n char combinations in strings handled incorrectly
  • Fixed: GENERATE method fails on JSON attribute names containing spaces

Note 2650040

  • New: Support for deserialization of OData Edm.Guid 
  • New: Support of Enum data types in ABAP. From SAP_BASIS 7.51, below - enums are ignored.
  • New: Support of conversion exits for serializing and deserializing.
  • Fixed: SERIALIZE method delivers an invalid JSON string, when NUMC type, filled with spaces is used.

Note 2629179

  • New: JSON timestamp fields, serialized in OData Edm.DateTime format (e.g. "\/Date(1467981296000)\/") are supported, and properly deserialized in ABAP date, time or timestamp fields
  • New: JSON timestamp fields, serialized in OData Edm.Time format (e.g. "PT10H34M55S") are supported, and properly deserialized in ABAP date, time, or timestamp fields
  • Fixed: content is scrambled, when using GENERATE method for JSON objects with a name containing special characters (for example "__metadata")
  • Fixed: GENERATE method does not consider custom name mapping pairs passed as a parameter for CONSTRUCTOR or GENERATE methods
  • Fixed: generation of very long integers (serialized numeric date) fails, due to I type overflow (you get 0 instead of an expected number) 

Note 2526405

  • Fixed: Deserialization of the inconsistent data (JSON string into ABAP table) leads to a short dump if the JSON string is empty.
  • Fixed: Serialization of data with includes with defined suffix (RENAME WITH SUFFIX) dumps
  • Fixed: GENERATE method fails, if the JSON object contains duplicate attributes and PRETTY_MODE-CAMEL_CASE is not used.
  • Fixed: GENERATE method fails, if JSON object contains attribute names longer than 30 characters (allowed ABAP field length). Can also occur in case the name is shorter than 30 characters, but PRETTY_MODE-CAMEL_CASE is used.
  • New: methods DUMP_INT, DUMP_TYPE, RESTORE_TYPE, and RESTORE can be overridden now. So, you can introduce your own data type conversion on serialization and deserialization.
  • New: now it is possible to pass the name mapping table as a parameter for the constructor/serialize/deserialize and control the way JSON names are formatted/mapped to ABAP names. This may help if you need special rules for name formattings (for special characters or two long JSON attributes) and standard pretty printing modes cannot help. With this feature, you may eliminate the need for the class extension and redefine PRETTY_NAME and PRETTY_NAME_EX methods. 
  • New: PRETTY_NAME_EX method extended to support the encoding of more special characters (characters needed in JSON names but that can not be used as part of ABAP name). The supported characters are: "!#$%&*-~/:|@.". Used with pretty_mode-extended.
  • New/UI2/CL_DATA_ACCESS class for working with dynamic ABAP data object (generated with method /UI2/CL_JSON=>GENERATE). The class can be used as a replacement for multiple ASSIGN COMPONENT language constructions. 

Note 2292558

  • Fixed: Empty JSON objects, serialized as entries of the table, are not deserialized into corresponding ABAP structures and further parsing of the JSON string after an empty object is skipped.
  • Fixed: JSON fields containing stringified timestamp representation in ISO 8601 format are not deserialized properly in the corresponding ABAP timestamp field.

Note 2300508

  • Fixed: Recursive (hierarchical) JSON objects cannot be deserialized.

Note 2330592

  • Fixed: Partial serialization/deserialization of the JSON is not supported
  • New: Extending of the class is supported
  • New: Added support for serializing named include structures from ABAP as embedded sub-objects in JSON

Note 2368774

  • Fixed: /UI2/CL_JSON creates unnecessary wrapping JSON object around value for name/value (table with 2 fields) tables with 1 unique key

  • Fixed: Performance of serialization/deserialization of big tables into/from JSON associative arrays (maps) is slow
  • Fixed: When trying to deserialize invalid (not matching) structure from JSON to ABAP dump OBJECTS_MOVE_NOT_SUPPORTED occurs

Note 2382783

  • Fixed: Unescape of symbol '\' on JSON deserialization does not work

  • Fixed: Short dump on serialization of classes with protected/private attributes
  • Fixed: Short dump when serializing dynamic, not existing, types

Note 2429758

  • Fixed: Short Dump on deserialization of classes with read-only attributes

  • New: Serialization parameter added NUMC_AS_STRING, controlling the way how NUMC fields are serialized. The default is FALSE. If set to TRUE, NUMC fields serialized not as numbers, but as strings, with all leading zeroes. Deserialization works compatibly with both ways of NUMC serialized data.
  • New: GENERATE and GENERATE_INT methods are added for on the fly creation of ABAP data objects from JSON, without the need to have a predefined ABAP structure. Supports automatic creation of ABAP structures, tables, and elementary types, concerning JSON types. Supports structure/table nesting.
  • New: DESERIALIZE_INT method throws an exception CX_SY_MOVE_CAST_ERROR and stops further processing in case of malformed data found and STRICT_MODE parameter in constructor set to TRUE.
  • New: Added support of XSTRING as input for deserialization.

Note 2480119

  • New: GENERATE method creates local custom class for deserialization (lc_json_custom), instead of standard /ui2/cl_json
  • Fixed: Internal tables are not initialized when deserializing JSON with empty arrays
  • New: Deserialization into a field with REF TO data type, if the field is bound, using a referenced data type
  • New: Deserialization uses automatic generation of the data if the field has "REF TO DATA" type and bound data is initial
上一篇:abapGit


下一篇:ABAP初学者如何系统地学习ABAP编程?