EPO Consulting Wiki - JSON API in SAP ABAP


einfache JSON - Erstellung

Die Klasse /EPO1/CL_TOOLS stellt die Methode ABAP_TO_JSON bereit, mit der eine beliebige ABAP - Struktur nach JSON umgewandelt werden kann. Primitive Werte (Zahlen, Zeichenketten, Tabellen etc.) ohne Struktur können nicht verarbeitet werden - es ist zumindest eine Struktur mit einem Strukturelement notwendig.


Beispiel - Typdefinition und Datendefinition:

  TYPES:
    lty_char_tab TYPE STANDARD TABLE OF char10 WITH KEY table_line,

    BEGIN OF lty_struc,   " Struktur mit primitiven Elementen und Tabellen
      eins  TYPE i,
      zwei  TYPE char10,
      drei  TYPE string,
      vier  TYPE xstring,
      fuenf TYPE lty_char_tab,
      sechs TYPE lty_char_tab,  " diese Tabelle bleibt leer
    END OF lty_struc,

    BEGIN OF lty_json,
      a TYPE i,
      b TYPE char10,
      c TYPE lty_struc,
    END OF lty_json.

  DATA:
    ls_json TYPE lty_json,   " ABAP - Struktur
    lv_json TYPE string.     " JSON - String


Beispieldaten:

  ls_json-a = 5.
  ls_json-b = 'Ab/\"'.

  ls_json-c-eins = 7.
  ls_json-c-zwei = 'dummy!{\}'.
  CONCATENATE
      'Text mit Tabulator'
      cl_abap_char_utilities=>horizontal_tab
      'und Zeilenumbruch'
      cl_abap_char_utilities=>newline
      'ENDE'
    INTO ls_json-c-drei.
  ls_json-c-vier = '0A000D00000A000D'.
  APPEND 'test1' TO ls_json-c-fuenf.
  APPEND 'test2' TO ls_json-c-fuenf.
  APPEND 'test3' TO ls_json-c-fuenf.


Beispiel Aufruf:

  CALL METHOD /epo1/cl_tools=>abap_to_json(
    EXPORTING
      i_any            = ls_json   " beliebige ABAP Datenstruktur
      i_strip_initial  = 'X'       " keine leeren Elemente ausgeben
    IMPORTING
      e_json           = lv_json   " JSON Ergebnisstring
       ).


Ergebnisbeispiel:

{"A":5,"B":"Ab/\\\"","C":{"EINS":7,"ZWEI":"dummy!{\\}","DREI":"Text mit Tabulator\tund Zeilenumbruch\nENDE","VIER":"CgANAAAKAA0=","FUENF":["test1","test2","test3"],"SECHS":[]}}

 

Formatierung

Die Methode MAP hat noch weitere optionale Parameter für die Formatierung des JSON - Strings zur besseren Lesbarkeit:

  • Zeilenumbruch einschalten
i_line_break = 'X'
  • zusätzlich eine Einrückung generieren
i_intent = 'X'


Beispiel für Zeilenumbruch:

{
"A":5,
"B":"Ab/\\\"",
"C":
{
"EINS":7,
"ZWEI":"dummy!{\\}",
"DREI":"Text mit Tabulator\tund Zeilenumbruch\nENDE",
"VIER":"CgANAAAKAA0=",
"FUENF":
[
"test1",
"test2",
"test3"
],
"SECHS":
[]
}
}


Beispiel für Zeilenumbruch und Einrückung:

{
 "A":5,
 "B":"Ab/\\\"",
 "C":
 {
  "EINS":7,
  "ZWEI":"dummy!{\\}",
  "DREI":"Text mit Tabulator\tund Zeilenumbruch\nENDE",
  "VIER":"CgANAAAKAA0=",
  "FUENF":
  [
   "test1",
   "test2",
   "test3"
  ],
  "SECHS":
  []
 }
}

 

Feldmapping mit Transformation

Wenn das generierte JSON als Request für ein Webservice benutzt wird, müssen die Feldnamen dem angebotenen API entsprechen. Über eine kundenspezifische Transformation können die Feldnamen beliebig gemappt werden.

In diesem Beispiel wollen wir das ABAP - Feld 'A' auf das JSON - Feld 'a' mappen, und 'B' auf 'be'. Zu beachten ist, daß die Transformation auf ein JSON-XML angewendet wird, hier stehen die Feldnamen jeweils im Attribut 'name'.

Das äusserste JSON-Objekt mit dem Namen 'ABAP_VALUES ist ein intern benutztes Hilfsobjekt, dieses wird vor der JSON-Ausgabe wieder entfernt.


Beispiel - JSON-XML (für die Daten lt. obigem Beispiel)

Hinweis: dieses XML wird SAP-intern generiert und muß nicht selbst erstellt werden

<?xml version="1.0" encoding="utf-8"?>
<object>
  <object name="ABAP_VALUES">
    <num name="A">5</num>
    <str name="B">Ab/\"</str>
    <object name="C">
      <num name="EINS">7</num>
      <str name="ZWEI">dummy!{\}</str>
      <str name="DREI">Text mit Tabulator	und Zeilenumbruch
ENDE</str>
      <str name="VIER">CgANAAAKAA0=</str>
      <array name="FUENF">
        <str>test1</str>
        <str>test2</str>
        <str>test3</str>
      </array>
    </object>
  </object>
</object>


Beispiel - Transformation: das Feld 'A' wird auf 'a' gemappt, das Feld 'B' auf 'be'

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

  <xsl:output encoding="UTF-8" indent="yes" method="xml" version="1.0"/>

  <!-- default - template (simple copy) -->
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="@name">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()" />
    </xsl:copy>

    <xsl:attribute name="name">
      <xsl:choose>
        <xsl:when test="current() = string('A')">
          <xsl:value-of select="string('a')"/>
        </xsl:when>
        <xsl:when test="current() = string('B')">
          <xsl:value-of select="string('be')"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="."/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:attribute>
  </xsl:template>
</xsl:transform>


Aufruf mit kundenspezifischer Transformation:

  CALL METHOD /epo1/cl_tools=>abap_to_json(
    EXPORTING
      i_any            = ls_json
      i_line_break     = 'X'
      i_intent         = 'X'
      i_strip_initial  = 'X'
      i_transformation = 'Z_JSON_MAPPING'  " Transformation entsprechend dem Beispiel
    IMPORTING
      e_json           = lv_json
       ).


Ergebnis:

{
 "a":5,
 "be":"Ab/\\\"",
 "C":
 {
  "EINS":7,
  "ZWEI":"dummy!{\\}",
  "DREI":"Text mit Tabulator\tund Zeilenumbruch\nENDE",
  "VIER":"CgANAAAKAA0=",
  "FUENF":
  [
   "test1",
   "test2",
   "test3"
  ]
 }
}

 

Feldmapping mit Customizingtabelle

Die fertig ausgelieferte Transformation /EPO1/EXC_JSON_FIELD_MAPPING erlaubt es, gemeinsam mit der Customizingtabelle /EPO1/FIELD_MAP das Mapping der ABAP-Felder auf JSON Feldnamen durchzuführen. Hier wird die statische Methode MAP der Klasse /EPO1/CL_FIELD_NAME_MAPPING benutzt, um über die Customizingtabelle das Mapping durchführt.

Vorteil ist, daß man hierzu keine Transformation schreiben muß, sondern mit einfachem Customizing (Transaktion SM30) auskommt.

Eine Voraussetzung für ein erfolgreiches Mapping ist, daß die Felder in der ABAP Struktur eindeutige Namen haben, wenn die JSON Feldnamen unterschiedlich sein sollen.

 

Mappingtabelle /EPO1/FIELD_MAP

Schlüssel ist der Name des EPO Services, die Richtung (eingehend: JSON nach ABAP / ausgehend: ABAP nach JSON) und der Name des zu mappenden Feldes innerhalb der ABAP Struktur.


Beispiel: das ABAP - Feld 'A' wird auf das JSON-Feld 'Ahhh' gemappt, und 'B' nach 'bEee'. Als Servicenamen nehmen wir entweder ein bereits eingerichtetes EPO-Service, oder einen beliebigen Identifier - z.B. 'JSON_API':

Service Name Richtung ABAP JSON
JSON_API O A Ahhh
JSON_API O B bEee

Nun der Beispiel - Aufruf: Die Transformation und der JSON-API-Name werden als Parameter übergeben

  CALL METHOD /epo1/cl_tools=>abap_to_json(
    EXPORTING
      i_any            = ls_json
      i_line_break     = 'X'
      i_intent         = 'X'
      i_strip_initial  = 'X'
      i_transformation = '/EPO1/EXC_JSON_FIELD_MAPPING'  " ausgelieferte Transformation
      i_service_id     = 'JSON_API'                      " EPO-Service ID, entsprechend der Customizingtabelle
    IMPORTING
      e_json           = lv_json
       ).


..und das JSON - Ergebnis dazu:

{
 "Ahhh":5,
 "bEee":"Ab/\\\"",
 "c":
 {
  "eins":7,
  "zwei":"dummy!{\\}",
  "drei":"Text mit Tabulator\tund Zeilenumbruch\nENDE",
  "vier":"CgANAAAKAA0=",
  "fuenf":
  [
   "test1",
   "test2",
   "test3"
  ]
 }
}

 

Mappingtabelle /EPO1/FIELD_MAP - Konvertierungen

Optional kann in der Mappingtabelle eine Konvertiermethode für jene Felder eingetragen werden, für die es kein explizites Mapping gibt. Dazu erstellt man ein Eintrag mit einem leeren ABAP Feldnamen und einem der folgenden Schlüsselworte im externen JSON Feldnamen:

  • lowercase - übersetzt ABAP in JSON Kleinbuchstaben
  • UPPERCASE - übersetzt ABAP in JSON Großbuchstaben (dies ist das Defaultverhalten)
  • camelCase - übersetzt ABAP in JSON Kleinbuchstaben, ein Unterstrich mit nachfolgendem Buchstaben wird in einen Großbuchstaben umgewandelt
Beispiel: ABAP 'USER_NAME' wird nach JSON 'userName' umgesetzt
  • CamelCase - ..detto, das erste Zeichen wird auch in einen Großbuchstaben umgewandelt
Beispiel: ABAP 'USER_NAME' wird nach JSON 'UserName' umgesetzt

 

zurück von JSON nach ABAP

Die Klasse /EPO1/CL_TOOLS bietet analog die Methode JSON_TO_ABAP, mit der ein JSON in eine beliebige ABAP Struktur übergeführt werden kann. Auch hier gibt es die Möglichkeit, eine eigene Transformation anzugeben und auch das Feldmapping über die Customizingtabelle zu steuern.

Zu beachten ist, daß nur jene ABAP-Felder befüllt werden, deren Namen passen.

 

Simples Mapping

Beispielstruktur und Beispiel-JSON können aus dem obigen Beispiel entnommen werden

    CALL METHOD /epo1/cl_tools=>json_to_abap
      EXPORTING
        i_json           = lv_json
      IMPORTING
        e_any            = ls_json
        .

Dieses Beispiel funktioniert nur, wenn die ABAP - Strukturnamen genau den JSON - Feldnamen entsprechen (die JSON-Feldnamen dürfen auch klein geschrieben sein).

 

Mapping mit einer selbst geschriebenen Transformation

Die Transformation muß das Mapping in der Gegenrichtung durchführen. Analog dem obigen Beispiel wird hier das JSON-Feld 'a' auf das ABAP-Feld 'A' gemappt, und 'be' auf 'B':

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

  <xsl:output encoding="UTF-8" indent="yes" method="xml" version="1.0"/>


  <!-- default - template (simple copy) -->
  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
  </xsl:template>


  <!-- Alle NAME - Attribute mappen
       Die Zuweisung des Attributnamens darf erst nach dem COPY erfolgen, sonst
       wird der Wert wieder überschrieben -->
  <xsl:template match="@name">
    <xsl:copy>
      <xsl:apply-templates select="@* | node()" />
    </xsl:copy>

    <xsl:attribute name="name">
      <xsl:choose>
        <xsl:when test="current() = string('a')">
          <xsl:value-of select="string('A')"/>
        </xsl:when>
        <xsl:when test="current() = string('be')">
          <xsl:value-of select="string('B')"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="."/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:attribute>
  </xsl:template>

</xsl:transform>

Aufruf mit kundenspezifischer Transformation:

    CALL METHOD /epo1/cl_tools=>json_to_abap
      EXPORTING
        i_json           = lv_json
       i_transformation = 'Z_JSON_MAPPING_2'  " Transformation entsprechend dem Beispiel
      IMPORTING
        e_any            = ls_json
        .

 

Transformation mit Mappingtabelle

Die Richtung JSON nach ABAP wird in der Mappingtabelle /EPO1/FIELD_MAP mit der Richtung 'I' angesprochen, es kommt wieder die ausgelieferte Transformation /EPO1/EXC_JSON_NAME_MAPPING zum Einsatz.

Um die Beispiel-Struktur aus dem obigen Beispiel befüllen zu können, müssen die 'O'-Einträge der Customizingtabelle /EPO1/FIELD_MAP auf 'I'-Einträge kopiert werden. Üblicherweise wird ein Response aber anders aufgebaut sein als ein Request, somit sind die Feldnamen meist unterschiedlich.

Service Name Richtung ABAP JSON
JSON_API I A Ahhh
JSON_API I B bEee

JSON nach ABAP mit Mappingtabelle:

    CALL METHOD /epo1/cl_tools=>json_to_abap
      EXPORTING
        i_json           = lv_json
        i_transformation = '/EPO1/EXC_JSON_NAME_MAPPING'  " ausgelieferte Transformation
        i_service_id     = 'JSON_API'                     " EPO-Service ID, entsprechend der Customizingtabelle
      IMPORTING
        e_any            = ls_json
        .