How to consume complex JSON request?

Consuming complex requests are possible in aperïo. There are some limitations which comes from performance assumptions. Total memory per job is 16MB. As Iptor is focused on performance of aperïo the JSON parser is created as “direct indexing parser”, not DOM parser. DOM parser was much more slower in usage by RPG programs. NOTE: In the road map is to prepare full DOM JSON parser and possibility for programer to decide which one is used in aperïo. Currently the number of incoming parameters are limited to 1024.

The incoming jSON is indexed and the indexes are put into predefined 1024 array. Procedure reqGetValue() allows programmer to get value for any named element of JSON.

Example 1:

{
    "IptorAPI":"1.0",
    "method":"sourcingPolicyAvailability.get",
    "params":{
        "item":"STENE1",
        "customer":"MEH001",
        "order":111655,
        "warehouse":"MOB"
    },
    "id":"js0clvwr"
}

The JSON above is indexed and to get value of each element programmer needs only one command:

//to get value of parameter "item"
xxValue = reqGetValue('params':'item');

//to get value of "method"
xxValue = reqGetValue('method');


//to get entire JSON string object of "params"
xxStringValue = reqGetValue('params': *omit: *omit: *omit: xxPointer: xxLen);

//Then in xxStringValue contains "{"item":"STENE1","customer":"MEH001","order":111655,"warehouse":"MOB"}"
//But the String value is limited to 1024 characters
//the xxPointer contains pointer to memory where "params" value starts....
//The xxLen contains length in bytes the "params" value
//By using xxPointer and xxLen programmer can copy even long text and parse it once again

Then how to read more complex requests. If the number of parameters do not exceed 1024 then it could be done without any additional parsing JSON. If the request is more complex then it may required additional parsing process.

Example 2:

{
    "IptorAPI":"1.0",
    "method":"salesOrders.get",
    "params":{
        "orders":[
            12345,
            56789,
            23476,
            255433
        ]
    },
    "id":"js0clvwr"
}

The JSON above is indexed and to get value of each element programmer needs only one command:

//to get number of elements in "order" array
xxNum = reqGetNumOfElements('params':'orders[]');

//to get first element of array
xxOrder = reqGetValue('params':'orders[1]');

//to get third element of array
xxOrder = reqGetValue('params':'orders[3]');

Example 3:

{
    "IptorAPI":"1.0",
    "method":"salesOrders.update",
    "params":{
        "order":12345,
        "deiveryDate":"2019-02-22",
        "discounts":[
            {
                "discountId":"ID1",
                "percentage":10
            },
            {
                "discountId":"ID2",
                "amount":2
            }
        ],
        "texts":{
            "textLine1":"bla 123",
            "textLine2":"acb 123"
        }
    },
    "id":"js0clvwr"
}

To get values foe ach element You can use following commands in RPG program:

//to get number of elements in "discounts" array
xxNum = reqGetNumOfElements('params':'discounts[]');

//to get discountId from first element of array
xxDiscountId = reqGetValue('params':'discounts[1]:discountId');

//to get percentage from first element of array
xxDiscountId = reqGetValue('params':'discounts[1]:percentage');

//to get textLine1 value
xxText = reqGetValue('params':'texts:textLine1');


//etc...

If the incoming JSON is more complex then it can be parsed partially in RPG program. First, programmer must get small portion of incoming JSON into variable, then parse it.

Example 4:

{
    "IptorAPI":"1.0",
    "method":"salesOrders.update",
    "params":{
        "order":12345,
        "currency": "EUR",
        "lines":[
            {
                "line":10,
                "quantity":3,
                "price":12.57,
                "unit":"EACH"
            },
            {
                "line":20,
                "quantity":5,
                "unit":"BOX10"
            }
        ],
        "texts":{
            "textLine1":"bla 123",
            "textLine2":"acb 123"
        }
    },
    "id":"js0clvwr"
}

Programmer can get one line information into variable then parse it.

//get first line JSON string 
xxOneLine = reqGetValue('params': 'lines[1]');

//Create parsed JSON objects in memory 
xxJSONPointer = JSON_parse(%ADDR(xxOneLine): %LEN(xxOneLine));

//to get price for line
xxPrice = JSON_getValue(xxJSONPointer: %UCS2('price')); 

//Do not forgot to destroy parsed objects from memory
JSON_parserDestroy(xxJSONPointer);

//get second line JSON string 
xxOneLine = reqGetValue('params': 'lines[2]');

//Create parsed JSON objects in memory 
xxJSONPointer = JSON_parse(%ADDR(xxOneLine): %LEN(xxOneLine));

//to get quantity for line
xxQuantity = JSON_getValue(xxJSONPointer: %UCS2('quantity')); 
...

//Do not forgot to destroy parsed objects from memory
JSON_parserDestroy(xxJSONPointer);

Another example of reading complex request. This time request contains array with item and price as element of table See IAF100AP/QSAMPLES member: EXAMPLE023

{
    "method":"aa_test",
    "params":{
        "arrayParams":[
            {"item":"sampleItem1","price":123.45},
            {"item":"sampleItem2","price":67.89}
        ]
    }
}    
 **************************************************************** 
 * Subprocedure: lcl_processRequest                            ** 
 **************************************************************** 
P lcl_processRequest...                                           
P                 B                                               
D                 PI             9B 0                             
                                                                  
D lNumOfElem      S             10i 0                             
D lElem           S             10i 0                             
D lItem           S             35                                
D lPrice          S             30  9                             
 /FREE                                                            
                                                                  
  monitor;                                                        
                                                                  
    // USER CODE begin - entry parameters validation              
                                                                  
    if reqExist('params':'arrayParams[]');                        
      lNumOfElem = reqGetNumOfElements('params' :'arrayParams[]');
    else;                                                         
      return API_PARAMS_ERR;                                              
    endif;                                                                
                                                                          
    lElem = 1;                                                            
    dow lElem <= lNumOfElem;                                              
                                                                          
      lItem = *blanks;                                                    
      lPrice = 0;                                                         
      if reqExist('params':'arrayParams['+%trim(%char(lElem))+']:item');  
        lItem = reqGetValue( 'params'                                     
                       :'arrayParams['+%trim(%char(lElem))+']:item');     
      endif;                                                              
                                                                          
      if reqExist('params':'arrayParams['+%trim(%char(lElem))+']:price'); 
        lPrice = %dec(reqGetValue( 'params'                               
                   :'arrayParams['+%trim(%char(lElem))+']:price') :17 :4);
      endif;                                                              
                                                                          
      //Put received value into response                                  
      if lItem <> *blanks and lPrice <> 0;                    
        respAddElement('items' :'data' :'array');             
        respAddStringField('items' :'receivedItem' :lItem);   
        respAddNumericField('items' :'receivedPrice' :lPrice);
      endif;                                                  
                                                              
                                                              
      lElem += 1;                                             
    enddo;                                                    
                                                              
    // USER CODE end   - entry parameters validation          
                                                              
    // Add data messages into response                        
    XPERRC = lcl_addMessages();                               
                                                              
  on-error;                                                   
    return API_PARAMS_ERR;                                    
  endmon;                                                     

  // Return          
  return XPERRC;     
                     
 /END-FREE           
P                 E                                            

There is also posible to use other parsers not only included in aperio. Here is an example of using SQL parser to receive the same request See IAF100AP/QSAMPLES member: EXAMPLE023

{
    "method":"aa_test",
    "params":{
        "arrayParams":[
            {"item":"sampleItem1","price":123.45},
            {"item":"sampleItem2","price":67.89}
        ]
    }
}    
 ****************************************************************
 * Subprocedure: lcl_processRequest                            **
 ****************************************************************
P lcl_processRequest...                                          
P                 B                                              
D                 PI             9B 0                            
                                                                 
D lItem           S             35                               
D lPrice          S             30  9                            
D lTmpTxt_ptr     S               *   inz(*null)                 
D lTmpTxt         S          16000C   based(lTmpTxt_ptr)         
D lParams         S          16000C   varying                    
D lPointer        S               *                              
D lLen            S             10u 0                            
D lType           S              1A                              
D lExist          S               N                              
 /FREE                                                           
                                                                 
  monitor;                                                       
                                                                              
    // USER CODE begin - entry parameters validation                        
                                                                            
    //Get entire json object (in this case array) 'params.arrayParams'      
    //As we ask to get part of JSON object from request, the                
    // reqGetValue will not return any value. But put the result in         
    // lPointer and lLen.                                                   
    reqGetValue( 'params': 'arrayParams' :lType :lExist                     
                :lPointer :lLen);                                           
                                                                            
    //Here lTmpTxt points to the place in memory where request array exists 
    //NOTE: lTmpTxt starts from pointer provided by reqGetValue but         
    // the length ot lTmpTxt is 16000 char. So, it may happen that variable 
    // lTmpTxt covers also other important structures in memory.            
    lTmpTxt_ptr = lPointer;                                                 
                                                                            
    //To avoid to destroy other structures in memory we intentionally       
    // do not use lTmpTxt as variable. First we have to copy from lTmpTxt   
    // only part which is reserved for our request array. Variable lLen     
    // contains size of value in bytes. Assuming request is kept in    
    // DBCS (2 bytes per character) we use div(lLen :2) to calculate   
    // current length of value in characters.                          
    lParams = %subst(lTmpTxt :1 :%div(lLen :2));                       
                                                                       
    //Parse array using SQL. We use table in qtmp to temporary store   
    //array results..                                                  
    EXEC SQL                                                           
      create table qtemp/myRequest(jsondoc varchar(3200) CCSID 1208);  
                                                                       
    EXEC SQL                                                           
      insert into myRequest values :lParams;                           
                                                                       
    EXEC SQL                                                           
      declare C1 cursor for                                            
         SELECT t.item, t.price                                        
             FROM myRequest,                                           
                 JSON_TABLE(myRequest.jsondoc, 'lax $'                 
                     COLUMNS (                                         
                          item VARCHAR(35) PATH 'lax $.item',  
                          price float PATH 'lax $.price'       
                     )                                         
                 )as t;                                        
                                                               
    exec SQL open C1;                                          
    exec SQL FETCH C1 INTO :lItem, :lPrice;                    
    dow sqlcod = *zero;                                        
                                                               
      //Put received values into respnse                       
      if lItem <> *blanks and lPrice <> 0;                     
        respAddElement('items' :'data' :'array');              
        respAddStringField('items' :'receivedItem' :lItem);    
        respAddNumericField('items' :'receivedPrice' :lPrice); 
      endif;                                                   
                                                               
      exec SQL FETCH C1 INTO :lItem, :lPrice;                  
    enddo;                                                     
    exec SQL CLOSE c1;                                         
                                                      
    EXEC SQL                                          
      drop table myRequest;                           
                                                      
                                                      
    // USER CODE end   - entry parameters validation  
                                                      
    // Add data messages into response                
    XPERRC = lcl_addMessages();                       
                                                      
  on-error;                                           
    return API_PARAMS_ERR;                            
  endmon;                                             
                                                      
  // Return                                           
  return XPERRC;                                      
                                                      
 /END-FREE                                            
P                 E