1
votes

I would like to deserialize a JSON string to a structure defined like this:

TYPES: BEGIN OF json_subobject,
         c TYPE i,
         d TYPE decfloat34,
       END OF json_subobject.
TYPES: BEGIN OF json_object,
        a TYPE c LENGTH 10,
        b TYPE json_subobject,
       END OF json_object.

DATA: foo TYPE json_object.

I model this structure in JSON like this:

{
  "a":"FooBar",
  "b":{
    "c":9,
    "d":3.14
  }
}

Now, I wrote a simple program to try out the CALL TRANSFORMATION statement on the above JSON accompanied with the above structure definitions. The program should deserialize the JSON (hard-coded into the variable lv_xmls) and print out the contents of the resulting structure. The contents of the structure should match the contents of the original JSON. This is the program:

TYPES: BEGIN OF json_subobject,
         c TYPE i,
         d TYPE decfloat34,
       END OF json_subobject.
TYPES: BEGIN OF json_object,
         a TYPE c LENGTH 10,
         b TYPE json_subobject,
       END OF json_object.

DATA: foo TYPE json_object.

DATA: lv_xmls TYPE string VALUE '{"a":"FooBar","b":{"c":9,"d":3.14}}',
      lv_xmlb TYPE xstring.

TRY.
    lv_xmlb = cl_abap_codepage=>convert_to(
                source      = lv_xmls
                codepage    = `UTF-8`
                endian      = space
                replacement = '#'
                ignore_cerr = abap_false ).
  CATCH cx_parameter_invalid_range cx_sy_codepage_converter_init cx_sy_conversion_codepage cx_parameter_invalid_type.
    ENDTRY.

WRITE: |Deserializing JSON ...|.
NEW-LINE.

CALL TRANSFORMATION id SOURCE XML lv_xmlb RESULT XML = foo.

WRITE: '{'.
NEW-LINE.
WRITE: |  "a": "{ foo-a }",|.
NEW-LINE.
WRITE: |  "b": \{|.
NEW-LINE.
WRITE: |    "c": { foo-b-c },|.
NEW-LINE.
WRITE: |    "d": { foo-b-d },|.
NEW-LINE.
WRITE: |  \}|.
NEW-LINE.
WRITE: |\}|.

The output I would expect is:

Deserializing JSON ...
{
  "a":"FooBar",
  "b":{
    "c":9,
    "d":3.14
  }
}

But unfortunately, the output I get is:

Deserializing JSON ...
{
  "a":"",
  "b":{
    "c":0,
    "d":0
  }
}

It looks like the CALL TRANSFORMATION statement isn't doing anything to me.

Is there an ABAP guru who could show me how to use CALL TRANSFORMATION in this (hopefully) simple case? I already looked around and found this helpful GitHub repo demonstrating how to deserialize JSON to a class, but I'd prefer to deserialize JSON to a structure for simplicity. I'm not entirely sure how to use CALL TRANSFORMATION because I'm unfamiliar with XSLT and advanced XML features, so I would very much appreciate a ready-made solution if possible ...

Thanks a lot in advance,

Joshua

2
See also this, this and thisSuncatcher

2 Answers

4
votes

My answer is in two parts. The first one is the best suited solution for JSON, according to me, and the second part explains why your code doesn't work with CALL TRANSFORMATION ID.

PART 1:

You'd better use one of the SAP classes which are better suited for handling JSON. I prefer the class /UI2/CL_JSON, which is the most "open" class according to me, because it's the most publicized by SAP (see below documentation), although not officially supported (yes, that's difficult to understand, welcome to SAP world).

TYPES: BEGIN OF json_subobject,
         c TYPE i,
         d TYPE decfloat34,
       END OF json_subobject.
TYPES: BEGIN OF json_object,
         a TYPE c LENGTH 10,
         b TYPE json_subobject,
       END OF json_object.

DATA: foo TYPE json_object.

/ui2/cl_json=>deserialize(
    EXPORTING json = '{"a":"FooBar","b":{"c":9,"d":3.14}}'
    CHANGING  data = foo ).

ASSERT foo = VALUE json_object( 
    a   = 'FooBar' 
    b-c = 9 
    b-d = '3.14' ).

Reference documentations for more information:


PART 2:

Your code with CALL TRANSFORMATION ID doesn't work for two reasons:

  1. The JSON must always be a JSON object with members named after the root names (RESULT rootname1 = var1 rootname2 = var2). You defined only one root named "XML" so the JSON should be something like {"X-ML":...} (it's X-ML and not XML because of specific ABAP reason)
  2. The ABAP component names are stored internally in SAP in upper case (A, B, C, D) and the identity transformation ID is case-sensitive concerning the member names, so the JSON data should have the member names in upper case (yours has all names in lower case a, b, c, d).

Differently said, your code might work if your input JSON contains this value:

lv_xmls = '{"X-ML":{"A":"FooBar","B":{"C":9,"D":3.14}}}'.

Another solution could consist in creating a custom identity transformation which would convert the JSON member names to upper case and add a dummy root element. But that's another story.

1
votes

I'm not a good ABAP developer so I don't know if there is better code to do this, but this worked for me:

    TYPES: BEGIN OF json_subobject,
         c TYPE i,
         d TYPE decfloat34,
       END OF json_subobject.
TYPES: BEGIN OF json_object,
         a TYPE c LENGTH 10,
         b TYPE json_subobject,
       END OF json_object.

DATA: foo TYPE json_object,
      writer TYPE REF TO cl_sxml_string_writer,
      json TYPE xstring.

DATA: lv_xmls TYPE string VALUE '{"a":"FooBar","b":{"c":9,"d":3.14}}',
      lv_xmlb TYPE xstring.

*TRY.
*    lv_xmlb = cl_abap_codepage=>convert_to(
*                source      = lv_xmls
*                codepage    = `UTF-8`
*                endian      = space
*                replacement = '#'
*                ignore_cerr = abap_false ).
*  CATCH cx_parameter_invalid_range cx_sy_codepage_converter_init cx_sy_conversion_codepage cx_parameter_invalid_type.
*    ENDTRY.



WRITE: |Deserializing JSON ...|.
NEW-LINE.

cl_fdt_json=>json_to_data( EXPORTING iv_json = lv_xmls
CHANGING ca_data = foo ).

WRITE: '{'.
NEW-LINE.
WRITE: |  "a": "{ foo-a }",|.
NEW-LINE.
WRITE: |  "b": \{|.
NEW-LINE.
WRITE: |    "c": { foo-b-c },|.
NEW-LINE.
WRITE: |    "d": { foo-b-d },|.
NEW-LINE.
WRITE: |  \}|.
NEW-LINE.
WRITE: |\}|.

This code just transforms a JSON array to an internal table.