[ABAP] JSON parsen und in Node-Table wandeln

* Quelle: https://github.com/i042416/KnowlegeRepository/blob/master/ABAP/SmallApp/011_ZCL_JERRY_TOOL.abap
CLASS lcl_json_parser DEFINITION.
  PUBLIC SECTION.

    TYPES: BEGIN OF ty_node,
             type      TYPE string,
             prefix    TYPE string,
             name      TYPE string,
             nsuri     TYPE string,
             value     TYPE string,
             value_raw TYPE xstring,
           END OF ty_node.

    TYPES: ty_it_node TYPE STANDARD TABLE OF ty_node WITH DEFAULT KEY.

    CONSTANTS: co_open_element TYPE string VALUE 'OPEN'.
    CONSTANTS: co_close_element TYPE string VALUE 'CLOSE'.
    CONSTANTS: co_attribute TYPE string VALUE 'ATTRIBUTE'.
    CONSTANTS: co_value TYPE string VALUE 'VALUE'.

    CLASS-METHODS: convert_json_to_node_table
      IMPORTING
                iv_json            TYPE string
      RETURNING VALUE(rv_it_nodes) TYPE ty_it_node
      RAISING
                cx_sxml_parse_error.
ENDCLASS.

CLASS lcl_json_parser IMPLEMENTATION.
  METHOD convert_json_to_node_table.

* JSON nach XSTRING (UTF-8) konvertieren
    DATA(o_json) = cl_abap_codepage=>convert_to( iv_json ).
* XSTRING einlesen und nach XML parsen
    DATA(o_reader) = cl_sxml_string_reader=>create( o_json ).

    TRY.
* erste Node holen
        DATA(o_node) = o_reader->read_next_node( ).

* solange es Nodes gibt
        WHILE o_node IS BOUND.
* Node-Typen prüfen
          CASE o_node->type.
* Öffnen-Node
            WHEN if_sxml_node=>co_nt_element_open.
              DATA(op) = CAST if_sxml_open_element( o_node ).
              APPEND VALUE #( type   = co_open_element
                              prefix = op->prefix
                              name   = op->qname-name
                              nsuri  = op->qname-namespace ) TO rv_it_nodes.

* Attribute
              LOOP AT op->get_attributes( ) ASSIGNING FIELD-SYMBOL(<a>).
                APPEND VALUE #( type      = co_attribute
                                prefix    = <a>->prefix
                                name      = <a>->qname-name
                                nsuri     = <a>->qname-namespace
                                value     = COND #( WHEN <a>->value_type = if_sxml_value=>co_vt_text THEN <a>->get_value( ) )
                                value_raw = COND #( WHEN <a>->value_type = if_sxml_value=>co_vt_raw THEN <a>->get_value_raw( ) ) ) TO rv_it_nodes.
              ENDLOOP.
* Schließen-Node
            WHEN if_sxml_node=>co_nt_element_close.
              DATA(cl) = CAST if_sxml_close_element( o_node ).
              APPEND VALUE #( type   = co_close_element
                              prefix = cl->prefix
                              name   = cl->qname-name
                              nsuri  = cl->qname-namespace ) TO rv_it_nodes.

* Wert-Node
            WHEN if_sxml_node=>co_nt_value.
              DATA(val) = CAST if_sxml_value_node( o_node ).
              APPEND VALUE #( type      = co_value
                              value     = COND #( WHEN val->value_type = if_sxml_value=>co_vt_text THEN val->get_value( ) )
                              value_raw = COND #( WHEN val->value_type = if_sxml_value=>co_vt_raw THEN val->get_value_raw( ) ) ) TO rv_it_nodes.
* Andere Nodetypen
            WHEN OTHERS.

          ENDCASE.

* nächste Node
          o_node = o_reader->read_next_node( ).
        ENDWHILE.

      CATCH cx_root INTO DATA(e_txt).
        RAISE EXCEPTION TYPE cx_sxml_parse_error
          EXPORTING
            error_text = e_txt->get_text( ).
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

START-OF-SELECTION.
  TRY.
* JSON-Beispiele
*      DATA(lv_json) = CONV string( '{"success":"true","msg":"Ok.","matnr":"0000001234"}' ).
      DATA(lv_json) = CONV string( '{"VALUES":[{"NAME":"Horst","TITLE":"Herr","AGE":30},{"NAME":"Jutta","TITLE":"Frau","AGE":35},{"NAME":"Ingo","TITLE":"Herr","AGE":31}]}' ).

* JSON -> Nodetable
      DATA(it_node_values) = lcl_json_parser=>convert_json_to_node_table( lv_json ).

* Datenausgabe
      cl_demo_output=>write_data( lv_json ).
      cl_demo_output=>write_data( it_node_values ).
      cl_demo_output=>display( ).
    CATCH cx_root INTO DATA(e_txt).
      WRITE: / e_txt->get_text( ).
  ENDTRY.

[ABAP] Einfacher REST-Service (ohne OData)

Piyush Philip auf sap.com hat eine simple Möglichkeit beschrieben, wie man in ABAP einen REST-Service mit den entsprechenden CRUD-Methoden implementieren kann. Dazu ist im wesentlichen nur eine eigene Handler-Klasse zu implementieren, die von IF_HTTP_EXTENSION erbt. Diese Handler-Klasse wird von einem Service des ICF gerufen.

  1. SE80 -> Neue Klasse anlegen
  2. In der Klasse im Reiter Interfaces: vom Interface IF_HTTP_EXTENSION erben
  3. die Interface-Methode IF_HTTP_EXTENSION~HANDLE_REQUEST implementieren (implementiert nur REST, kein OData-Sevice!)
    METHOD if_http_extension~handle_request.
    * Typen
      TYPES: BEGIN OF ty_json_request,
               param1 TYPE char64,
               param2 TYPE char64,
             END OF ty_json_request.
    
      TYPES: BEGIN OF ty_json_response,
               success   TYPE string,
               msg       TYPE string,
               operation TYPE string,
             END OF ty_json_response.
    
    * Variablen
      DATA: rv_response TYPE ty_json_response.
      DATA: it_path_info TYPE stringtab.
      DATA: it_query_strings TYPE stringtab.
    
    * Header Fields auslesen
      DATA: it_header_fields TYPE tihttpnvp.
      server->request->get_header_fields( CHANGING fields = it_header_fields ).
    
    * Request Methode (POST, GET, PUT, DELETE ...)
      DATA(lv_request_method) = server->request->get_header_field( name = '~request_method' ).
    
    * URL Pathinfo
      DATA(lv_path_info) = server->request->get_header_field( name = '~path_info' ).
    * erstes '/' entfernen
      SHIFT lv_path_info LEFT BY 1 PLACES.
      SPLIT lv_path_info AT '/' INTO TABLE it_path_info.
    
    * URL Parameter (Query Strings) für Auswertung
      DATA(lv_query_string) = server->request->get_header_field( name = '~query_string' ).
      SPLIT lv_query_string AT '&' INTO TABLE it_query_strings.
    
    * Request Methoden behandeln
      CASE lv_request_method.
    * C (Create)
        WHEN 'POST'.
    * Beispiel: per Request übermittelte JSON-Daten auslesen und behandeln
    * Content-Typ auslesen
          DATA(lv_request_content_type) = server->request->get_content_type( ).
    
    * Wenn Content-Typ JSON
          IF lv_request_content_type CP 'application/json'.
    * JSON-Daten auslesen und nach ABAP-Typ konvertieren -> Parameternamen müssen übereinstimmen, sonst HTTP 500!
            DATA(lv_request_cdata) = server->request->get_cdata( ).
    
            DATA: lv_request_abap TYPE ty_json_request.
    
            TRY.
                /ui2/cl_json=>deserialize( EXPORTING json = lv_request_cdata
                                                     pretty_name = /ui2/cl_json=>pretty_mode-camel_case
                                           CHANGING  data = lv_request_abap ).
    
              CATCH cx_root INTO DATA(e_txt).
                MESSAGE e_txt->get_text( ) TYPE 'S' DISPLAY LIKE 'E'.
            ENDTRY.
    
            rv_response = VALUE #( success   = 'true'
                                   msg       = lv_request_abap-param1 && ' | ' && lv_request_abap-param2
                                   operation = lv_request_method ).
          ELSE.
            rv_response = VALUE #( success   = 'false'
                                   msg       = 'Request content type must be application/json'
                                   operation = lv_request_method ).
          ENDIF.
    * R (Read)
        WHEN 'GET'.
    * Wert des URL-Parameters 'param' lesen
          DATA(lv_param) = server->request->get_form_field( 'param' ).
    
          rv_response = VALUE #( success   = 'true'
                                 msg       = lv_param
                                 operation = lv_request_method ).
    * U (Update)
        WHEN 'PUT'.
          rv_response = VALUE #( success   = 'true'
                                 msg       = ''
                                 operation = lv_request_method ).
    * D (Delete)
        WHEN 'DELETE'.
          rv_response = VALUE #( success   = 'true'
                                 msg       = ''
                                 operation = lv_request_method ).
    
    * unerlaubte Methoden abfangen
        WHEN OTHERS.
    * Fehlercode 405 zurückgeben
          server->response->set_status( code   = '405'
                                        reason = 'method not allowed' ).
    
          server->response->set_header_field( name  = 'allow'
                                              value = 'POST, GET, PUT, DELETE' ).
    
          rv_response = VALUE #( success   = 'false'
                                 msg       = 'error'
                                 operation = lv_request_method ).
      ENDCASE.
    
    * Service-Response als JSON zurückgeben
      DATA(o_conv) = NEW /ui2/cl_abap2json( ).
      DATA(lv_json_str) = o_conv->struc2json( iv_struc = rv_response ).
    
      server->response->set_header_field( name  = 'Content-Type'
                                          value = 'application/json; charset=iso-8859-1' ).
    
      server->response->set_cdata( data = lv_json_str ).
    
    ENDMETHOD.
    
  4. SICF -> neue ICF-Node (Sub-Element) unterhalb default_host -> sap -> bc als neuen unabhängigen Service anlegen
  5. in der Handlerliste des Service die erstellte Klasse als „Handler“ angeben
  6. auf die neue ICF-Node rechtsklicken -> Service aktivieren
  7. auf die neue ICF-Node rechtsklicken -> Service testen
  8. URL aus Browserfenster kopieren (http://domain.de/sap/bc/servicename?sap-client=900)
  9. im Browser mir einem Rest-Plugin (FireFox-Prugin: Rested) testen

Links

[SAP Gateway] Einfacher REST-Webservice / CRUD-Applikation

Links

[ABAP] String -> JSON

DATA: text TYPE string VALUE 'Hello world!'.

* ABAP (string) -> JSON
DATA(o_writer_json) = cl_sxml_string_writer=>create( type = if_sxml=>co_xt_json ).
CALL TRANSFORMATION id SOURCE text = text RESULT XML o_writer_json.
DATA(json) = cl_abap_codepage=>convert_from( o_writer_json->get_output( ) ).

WRITE: / json.

[ABAP] itab -> JSON

* Variante 1 (CALL TRANSFORMATION)
TYPES: BEGIN OF s_person,
         name  TYPE string,
         title TYPE string,
         age   TYPE i,
       END OF s_person.

TYPES: t_person TYPE STANDARD TABLE OF s_person WITH DEFAULT KEY.

DATA(it_persons) = VALUE t_person( ( name = 'Horst' title = 'Herr' age = 30 )
                                   ( name = 'Jutta' title = 'Frau' age = 35 )
                                   ( name = 'Ingo' title = 'Herr' age = 31 ) ).

* ABAP (iTab) -> JSON
DATA(o_writer_itab) = cl_sxml_string_writer=>create( type = if_sxml=>co_xt_json ).
CALL TRANSFORMATION id SOURCE values = it_persons RESULT XML o_writer_itab.
DATA: json TYPE string.
cl_abap_conv_in_ce=>create( )->convert( EXPORTING
                                          input = o_writer_itab->get_output( )
                                        IMPORTING
                                          data = json ).

WRITE: / json.

* Variante 2 (/ui2/cl_abap2json)
SELECT matnr, mtart, meins, pstat
  INTO TABLE @DATA(it_mara)
  FROM mara
  UP TO 10 ROWS.

* ABAP (iTab) -> JSON
DATA(o_conv) = NEW /ui2/cl_abap2json( ).
DATA(lv_str) = o_conv->table2json( it_data = it_mara ).

WRITE: / lv_str.

[ABAP] JSON -> itab

TYPES: BEGIN OF s_person,
         name  TYPE string,
         title TYPE string,
         age   TYPE i,
       END OF s_person.

TYPES: t_person TYPE STANDARD TABLE OF s_person WITH DEFAULT KEY.

DATA: json TYPE string VALUE '{"VALUES":[{"NAME":"Horst","TITLE":"Herr","AGE":30},{"NAME":"Jutta","TITLE":"Frau","AGE":35},{"NAME":"Ingo","TITLE":"Herr","AGE":31}]}'.
DATA(it_persons) = VALUE t_person( ).

* JSON -> ABAP (iTab)
CALL TRANSFORMATION id SOURCE XML json RESULT values = it_persons.

IF lines( it_persons ) > 0.
  WRITE: / it_persons[ 1 ]-name.
ENDIF.

[ABAP] itab -> JSON (trex)

TYPES: BEGIN OF s_person,
         name  TYPE string,
         title TYPE string,
         age   TYPE i,
       END OF s_person.

TYPES: t_person TYPE STANDARD TABLE OF s_person WITH DEFAULT KEY.

DATA(it_persons) = VALUE t_person( ( name = 'Horst' title = 'Herr' age = 30 )
                                   ( name = 'Jutta' title = 'Frau' age = 35 )
                                   ( name = 'Ingo' title = 'Herr' age = 31 ) ).

* ABAP (iTab) -> JSON (trex)
DATA(o_trex) = NEW cl_trex_json_serializer( it_persons ).
o_trex->serialize( ).

WRITE: / o_trex->get_data( ).

[JavaScript] String in JSON wandeln

Beispiel 1 (simpel)

// https://javascript.info/json

// JSON-Objekt definieren
let sJSON = '{ "name":"Heinz","age":"56","city":"Hamburg" }';

// JSON-Objekt aus String parsen
let oJSONpar = JSON.parse(sJSON);

// Testausgabe
console.log(oJSONpar.name);

Beispiel 2 (reviver)

// JSON-Objekt definieren
let sJSON = '{ "name":"Heinz","age":"56","city":"Hamburg","birth":"2018-04-25T12:00:00.000Z" }';

// JSON-Objekt aus String parsen
let oJSONpar = JSON.parse(sJSON, function(key, value) {

  // wenn Datumsfeld "birth"
  if (key == 'birth')
  {
      // Datumsobjekt zurückgeben
      return new Date(value);
  }
  else
  {
      return value;
  };
});

// Geburtsdatum ist jetzt vom Typ "Date" mit enspr. Funktionen
console.log(oJSONpar.birth.toLocaleString('de-DE'));