2014-01-06

Improvement to SAP JSON RFC service

In my last post, I described an essential piece of the SAP UI5 development as being the JSON HTTP handler.  This week I enhanced the service to allow more complex data types to be passed into the RFC.

The issue I encountered was this: the function module I was calling had input values that were more than simple data types.  It appears that the JSON HTTP handler as designed by Cesar would allow me to pass the JSON data directly in from HTTP.  However, since my SAP UI5 in JavaScript skills are currently much weaker than my ABAP I wanted a simple way to pass this data as a URL parameter to the SAP HTTP handler and let it do the work of converting the input to JSON.  Here’s what I did. 

I added two new private methods to the HTTP hander class:

MAP_QS2JSON: A recursive routine that reads the query string and determines whether the name is a table/structure/simple type.  This determination is made by appending [], {}, or nothing to the name of the query parameter.

METHOD map_qs2json.
*****************************************************************zkegm**
* Purpose: Map query string name/values to JSON format.  Rewrote this  *
*          to handle table data input also.                            *
*----------------------------------------------------------------------*
* -Changed by-       -Date-     -Req #-  -Action-                      *
* Jeff Woehler       2014.01.01 PLM8772  Initial.                      *
************************************************************************
 
* We map the query string to a simple JSON input. Handy for REST style queries.
* The query string may come from GET requests in the url and content data in
* POST request in x-www-form-urlencoded. ICF handles this perfectly and mixes both!! Great!!
 
  DATA: lv_lines                      TYPE i,
        lv_idx                        TYPE i.
  DATA: ls_save_nvp                   TYPE ihttpnvp,
        lt_nvp                        TYPE tihttpnvp,
        lt_tr_nvp                     TYPE tihttpnvp,
        lv_tr_json                    TYPE string,
        lv_name                       TYPE string,
        lv_len                        TYPE i,
        lv_table                      TYPE xfeld.
  FIELD-SYMBOLS <ls_nvp> TYPE ihttpnvp.
 
  lt_nvp[] = it_qs_nvp[].
  SORT lt_nvp[] BY name.
 
  lv_lines = lines( lt_nvp[] ).
  CLEAR: lv_idx, lv_table.
 
  LOOP AT lt_nvp ASSIGNING <ls_nvp>.
    ADD 1 TO lv_idx.
 
    " ABAP is upper case internally anyway.
    TRANSLATE <ls_nvp>-name TO UPPER CASE.
 
    IF lv_table = abap_true.
      IF <ls_nvp>-name NE ls_save_nvp-name.
        CONCATENATE rv_json ']'
               INTO rv_json RESPECTING BLANKS.
        lv_table = abap_false.
      ENDIF.
    ENDIF.
 
    IF lv_idx NE 1.
      CONCATENATE rv_json ','
             INTO rv_json RESPECTING BLANKS.
    ENDIF.
 
    IF <ls_nvp>-name CS '[]'.
      lv_len = strlen( <ls_nvp>-name ) - 2.
      lv_name = <ls_nvp>-name(lv_len).
 
      IF <ls_nvp>-name NE ls_save_nvp-name.
        lv_table = abap_true.
        CONCATENATE rv_json '"' lv_name '":['
               INTO rv_json RESPECTING BLANKS.
      ENDIF.
 
      lt_tr_nvp = map_tblrow2nvp( iv_value      = <ls_nvp>-value
                                  iv_delimiter  = ';' ).
      lv_tr_json = map_qs2json( lt_tr_nvp ).
      CONCATENATE rv_json lv_tr_json
             INTO rv_json RESPECTING BLANKS.
    ELSEIF <ls_nvp>-name CS '{}'.
      lv_len = strlen( <ls_nvp>-name ) - 2.
      lv_name = <ls_nvp>-name(lv_len).
      lt_tr_nvp = map_tblrow2nvp( iv_value      = <ls_nvp>-value
                                  iv_delimiter  = ';' ).
      lv_tr_json = map_qs2json( lt_tr_nvp ).
      CONCATENATE rv_json '"' lv_name '":' lv_tr_json
             INTO rv_json RESPECTING BLANKS.
    ELSE.
      CONCATENATE rv_json '"' <ls_nvp>-name '":"' <ls_nvp>-value '"'
             INTO rv_json RESPECTING BLANKS.
    ENDIF.
 
    IF lv_idx < lv_lines.
    ENDIF.
 
    ls_save_nvp = <ls_nvp>.
  ENDLOOP.
 
  CONCATENATE '{' rv_json '}' INTO rv_json.
 
ENDMETHOD.

MAP_TBLROW2NVP: This routine splits the input structure value at a ‘;’ and creates a new table of name/value pairs for which to generate a JSON string.



METHOD map_tblrow2nvp.
*****************************************************************zkegm**
* Purpose: Map table-row to name/value pair.                           *
*----------------------------------------------------------------------*
* -Changed by-       -Date-     -Req #-  -Action-                      *
* Jeff Woehler       2014.01.01 PLM8772  Initial.                      *
************************************************************************
 
  DATA: lt_params                     TYPE TABLE OF string,
        lv_param                      TYPE string,
        ls_nvp                        TYPE ihttpnvp.
 
  SPLIT iv_value AT iv_delimiter INTO TABLE lt_params[].
 
  LOOP AT lt_params INTO lv_param.
    SPLIT lv_param AT '=' INTO ls_nvp-name ls_nvp-value.
    APPEND ls_nvp TO rt_nvp[].
  ENDLOOP.
 
ENDMETHOD.

Caveat: this improvement obviously wouldn’t handle nested levels of structure - example: a structure containing a field that is a structure.  But this is (I hope) a tiny improvement to the the wonderful work that Cesar Martin put together.


Now I can have an SAP UI5 refresh routine like this and ABAP will handle the JSON. 



  • I_TEMPORARY_FLAG: simple value
  • I_SEARCH_DATE: structure of fields – single row
  • IT_WERKS: table of data – multiple rows


refresh: function(event) {
    sap.ui.getCore().setModel(undefined);
    
        var url = 'http://kww-h10s.kimball.com:8002/keg/rfc/Z_E_RFC_GCS_AVG_TURN_TIME/lc?I_TEMPLATE=00001';
      
        var cbTemp = this.byId('cbTemp');
        if (cbTemp.getChecked() == true) {
            url += '&I_TEMPORARY_FLAG=X';
        } 
        
        var date1 = this.byId('date1');
        var date2 = this.byId('date2');
        url += '&I_SEARCH_DATE{}=SIGN=I;OPTION=BT;LOW=' + date1.getYyyymmdd() + ';HIGH=' + date2.getYyyymmdd();
        
        var lstPlant = this.byId('lstPlant');
        var plants = lstPlant.getSelectedKeys();
        for (var idx = 0; idx < plants.length; idx++) {
            url += '&IT_WERKS[]=SIGN=I;OPTION=EQ;WERKS_LOW=' + plants[idx];
        }
      
      var oModel4 = new sap.ui.model.json.JSONModel(url);
      sap.ui.getCore().setModel(oModel4);
}

Get my update to the handler here.  Be sure to read the spec and documentation from the original developer here.


Enjoy!