With the release of RPG-XML Suite version 3.3, Krengel Technology is bringing JSON to the IBM i.

This article was originally posted on our RPG-XML Suite product website, and has since been updated.

JSON is becoming increasingly popular in web service communication, and we are eager to help our customers take full advantage of this powerful, flexible data interchange format.

In this series of tutorials, we are demonstrating the JSON composition and parsing APIs that might be used to offer or communicate with a web service. In our previous installment (read it here: https://www.krengeltech.com/2016/10/composing-json-with-rpg-xml-suite-3-3/) we used RPG-XML Suite to compose a JSON object which represented contact information for a visitor at a trade show. In this tutorial, we are going to use the RXS JSON parsing API to read the data from an array of those contact objects.

The full source code for this program can be found below, and you can click the right-hand option in the toolbar to open the code in a new window. We will refer to this code throughout the post.

H DFTACTGRP(*NO) ACTGRP(*CALLER) BNDDIR('RXSBND')                         
                                                                          
 /COPY QRPGLECPY,RXSCB                                                    
                                                                          
 * This parsing subprocedure will be called by RXS_ParseJson(), and it    
 *  will write parsed JSON data to the job log.                           
D JsonHandler     PR              N                                       
D  pType                         5I 0 Const                               
D  pPath                              Const Like(RXS_Var64Kv_t)           
D  pIndex                       10U 0 Const                               
D  pData                          *   Const                               
D  pDataLen                     10U 0 Const                               
                                                                          
 * These fields are used during parsing to store address information      
D streetNbr       S                   Like(RXS_Var1Kv_t)                  
D streetName      S                   Like(RXS_Var1Kv_t)                  
D aptNbr          S                   Like(RXS_Var1Kv_t)                  
D city            S                   Like(RXS_Var1Kv_t)                  
D state           S                   Like(RXS_Var1Kv_t)                  
D zip             S                   Like(RXS_Var1Kv_t)                  
                                                                          
 * ParseJsonDS is used to configure the JSON parser                       
D ParseJsonDS     DS                  LikeDS(RXS_ParseJsonDS_t)           
D                                     Inz(*LikeDS)                        
D JSON            S                   Like(RXS_Var1Kv_t)                  
                                                                          
 /FREE                                                                    
  // reset datastructures                                                 
  RXS_ResetDS( ParseJsonDS : RXS_DS_TYPE_PARSEJSON );                    
                                                                          
  // this field holds a pointer to the subprocedure that will             
  //  handle the parsed JSON data                                         
  ParseJsonDS.Handler = %Paddr( JsonHandler );                            
  // by default, RXS_ParseJson() parses all data as though it is a string.
  //  This setting allows us to retrieve data as its associated JSON      
  //  datatype                                                            
  ParseJsonDS.ConvertDataToString = RXS_NO;                               
  // this is the IFS file path where the JSON document is stored          
  ParseJsonDS.Stmf = '/tmp/parseJsonDemo.json';                  
                                                                          
  // this calls the JSON parser, which will read the JSON data and        
  //  call the JsonHandler subprocedure on each event                     
  RXS_ParseJson( *Omit : ParseJsonDS );                                   
                                                                          
  // this cleans up the memory used by the JSON operations                
  RXS_DestroyJson( ParseJsonDS );                                         
                                                                          
  *INLR = *On;                                                            
  return;                                                                 
 /END-FREE                                                                
                                                                          
                                                                          
P JsonHandler     B                   Export                              
D                 PI              N                                       
D  pType                         5I 0 Const                               
D  pPath                              Const Like(RXS_Var64Kv_t)           
D  pIndex                       10U 0 Const                               
D  pData                          *   Const                               
D  pDataLen                     10U 0 Const                               
 * Will hold character data retrieved from pData                          
D Val             S                   Like(RXS_Var1Kv_t)                  
 * Will retrieve an indicator from a boolean element                      
D BoolVal         S               N   Based(pData)                        
 * Will retrieve an integer from a numeric element                        
D IntVal          S             20I 0 Based(pData)                        
 /FREE                                                                    
  // this main select block lets us target different types of             
  //  parsing events                                                      
  select;                                                                 
    // targets only array start events                                    
    when pType = RXS_JSON_ARRAY;                                          
      select;                                                             
        // start of the root array                                        
        when pPath = '[*]';                                               
          RXS_JobLog( 'Begin Parsing...' );                               
          RXS_JobLog( '******************************' );                 
          RXS_JobLog( 'Contact List:' );                                  
                                                                          
        // start of phone array                                           
        when pPath = '[*]/phone[*]';                                      
          RXS_JobLog( 'Contact Phone(s):' );                              
      endsl;                                                              
                                                                          
    // targets only end of array events                                   
    when pType = RXS_JSON_ARRAY_END;                                      
      select;                                                             
          // end of the root array                                        
          when pPath = '[*]';                                             
          RXS_JobLog( 'End Parsing.' );                                   
      endsl;                                                              
                                                                          
    // targets only object start events                                   
    when pType = RXS_JSON_OBJECT;                                         
      select;                                                             
        // start of new contact object                                    
        when pPath = '[*]';                                               
          RXS_JobLog( '******************************' );                 
                                                                          
        // start of address child object                                  
        // we're using this node to clear the fields that hold            
        //  address information to be printed                             
        when pPath = '[*]/address';                                       
          clear streetNbr;                                                
          clear streetName;                                               
          clear aptNbr;                                                   
          clear city;                                                     
          clear state;                                                    
          clear zip;                                                      
      endsl;                                                              
                                                                          
    // targets only end of object events                                  
    when pType = RXS_JSON_OBJECT_END;                                     
      select;                                                             
        // end of address object                                          
        // we'll use this event to write out the address data, which was  
        //  previously parsed, to the job log                             
        when pPath = '[*]/address';                                       
          RXS_JobLog( 'Contact Address:' );                               
          RXS_JobLog( x'05' + streetNbr + ' ' + streetName );             
          if %Len(aptNbr) > 0;                                            
            RXS_JobLog( x'05' + 'Apt. ' + aptNbr );                       
          endif;                                                          
          RXS_JobLog( x'05' + city + ', ' + state );                      
          RXS_JobLog( x'05' + zip );                                      
                                                                          
        // end of contact object                                          
        when pPath = '[*]';                                               
          RXS_JobLog( '******************************' );                 
      endsl;                                                              
                                                                          
  // the following criteria target the different data types that we       
  //  expect to be returned from the JSON data                            
    when pType = RXS_JSON_NULL;                                           
      // for any null value in our data, we want to skip any processing   
      //  and continue parsing the remaining data                         
                                                                          
    when pType = RXS_JSON_BOOLEAN;                                        
      select;                                                             
        // the only element we expect to contain boolean data is the      
        //  salesProspect element                                         
        when pPath = '[*]/salesProspect';                                 
          if BoolVal;                                                     
            RXS_JobLog( 'Sales Prospect?: Yes' );                         
          else;                                                           
            RXS_JobLog( 'Sales Prospect?: No' );                          
          endif;                                                          
      endsl;                                                              
                                                                          
    when pType = RXS_JSON_INTEGER;                                        
      select;                                                             
        when pPath = '[*]/address/number';                                
          streetNbr = %Char( IntVal );                                    
      endsl;                                                              
                                                                          
    when pType = RXS_JSON_STRING;                                         
      // for our string elements, we will retrieve the values as          
      //  character data using RXS_STR                                    
      select;                                                             
        when pPath = '[*]/name';                                          
          clear Val;                                                      
          Val = RXS_STR( pData : pDataLen );                              
          RXS_JobLog( 'Name: ' + Val );                                   
                                                                          
        when pPath = '[*]/address/street';                                
          streetName = RXS_STR( pData : pDataLen );                       
                                                                          
        when pPath = '[*]/address/apartment';                             
          aptNbr = RXS_STR( pData : pDataLen );                           
                                                                          
        when pPath = '[*]/address/city';                                  
          city = RXS_STR( pData : pDataLen );                             
                                                                          
        when pPath = '[*]/address/state';                                 
          state = RXS_STR( pData : pDataLen );                            
                                                                          
        when pPath = '[*]/address/postCode';                              
          zip = RXS_STR( pData : pDataLen );                              
                                                                          
        // this will be called for each phone number element in the array 
        when pPath = '[*]/phone[*]';                                      
          clear Val;                                                      
          Val = RXS_STR( pData : pDataLen );                              
          RXS_JobLog( x'05' + Val );                                      
                                                                          
        when pPath = '[*]/email';                                         
          clear Val;                                                      
          Val = RXS_STR( pData : pDataLen );                              
          RXS_JobLog( 'Email: ' + Val );                                  
      endsl;                                                              
  endsl;                                                                  
                                                                          
  return *On;                                                             
 /END-FREE                                                                
P                 E 

There are two main components to parsing JSON data with RPG-XML Suite.

The first is the main procedure, which will configure the JSON parsing engine, and the second is the parsing handler subprocedure, which processes retrieved JSON data.

The handler subprocedure is the most important – and the most complex – component in a JSON parsing program. This subprocedure will be called each time a JSON node is found in your document. The handler can be configured to process events or data when specific nodes are identified. Depending on your business use case, you might want to write the data to a physical file, or perform further processing on the data. An object start event, for instance, might be used to write a header section to an output document. In this example, we’re going to write the parsed data to the job log.

All JSON parsing handler subprocedures must have the following prototype:

D JsonHandler     PR              N
D  pType                         5I 0 Const
D  pPath                              Const Like(RXS_Var64Kv_t)
D  pIndex                       10U 0 Const
D  pData                          *   Const
D  pDataLen                     10U 0 Const

In order to build the parsing handler, we need to refer to the JSON document structure to identify the JSON elements and events that we want to retrieve. Here is the document we will be parsing:

[
  {
    "name": "Dona Franks",
    "salesProspect": true,
    "address": {
      "number": 20391,
      "apartment": "177",
      "street": "Central Avenue",
      "city": "Waikele",
      "state": "Minnesota",
      "postCode": "60247"
    },
    "phone": [
      "+1 (971) 596-2501",
      "+1 (989) 401-2094",
      "+1 (948) 493-2985"
    ],
    "email": "donafranks@gadtron.com"
  },
  {
    "name": "Ortega Stuart",
    "salesProspect": true,
    "address": {
      "number": 76308,
      "apartment": null,
      "street": "Delmonico Place",
      "city": "Fillmore",
      "state": "Virginia",
      "postCode": "94862"
    },
    "phone": [
      "+1 (977) 470-3280"
    ],
    "email": "ortegastuart@gadtron.com"
  },
  {
    "name": "Jackie Wilcox",
    "salesProspect": false,
    "address": {
      "number": 16574,
      "apartment": "223",
      "street": "Monitor Street",
      "city": "Whipholt",
      "state": "Washington",
      "postCode": "32492"
    },
    "phone": [
      "+1 (996) 561-3110",
      "+1 (875) 534-2432"
    ],
    "email": "jackiewilcox@gadtron.com"
  }
]

The handler subprocedure is built using select blocks to control which code is executed. For each node from which we need to retrieve data, we will need to write a corresponding “when” criterion.

Note that, because our document’s root element is an array, our paths all start with “[*]“, which indicates to the parsing engine that an array will be present at that level. If our root element was an object, the paths would begin with “/” instead. As such, in order to retrieve the data from the “name” element, we would use this path:

[*]/name

The same notation is used for arrays that are nested within objects, like with the array of phone numbers in our contact object:

[*]/phone[*]

Additionally, for objects and arrays, the same path can be used to detect the start and end of the element, as well as any element data. The above path for the phone array would fire an event once for the start of the array, once for each element in the array, and once again for the end of the array. These separate events can be captured within the parsing handler subprocedure to trigger processing specific to the collection of data.

The RXSCB copybook lists constants that represent each of the JSON data types and parsing events. We can reference these constants as criteria in a select block to control which code gets triggered based on the path, the type of event, and even on the data type of the element that was parsed. Using these constants, we can build the basic structure of our parsing handler like this:

select;
  when pType = RXS_JSON_ARRAY;
  …
  when pType = RXS_JSON_ARRAY_END;
  …
  when pType = RXS_JSON_OBJECT;
  …
  when pType = RXS_JSON_OBJECT_END;
  …
  When pType = RXS_JSON_BOOLEAN;
  …
  etc.
endsl;

Within each “when” selector, we will be writing code to handle the different paths that might trigger that event type. For example, we have two arrays in our JSON document – the root structure array, and the child array of phone numbers in each contact object. We want to do something special for the beginning of each of these arrays, so we can write another select block to address the different paths:

when pType = RXS_JSON_ARRAY;
  select;
    when pPath = '[*]';
      RXS_JobLog( 'Begin Parsing…' );
      RXS_JobLog( '******************************' );
      RXS_JobLog( 'Contact List:' );


    when pPath = '[*]/phone[*]';
      RXS_JobLog( 'Contact Phone(s):' );
  endsl;

We can also use the same pPath value of “[*]” within the RXS_JSON_ARRAY_END block to write out the end of the parsing operation:

when pType = RXS_JSON_ARRAY_END;
  select;
    when pPath = '[*]';
      RXS_JobLog( '******************************' );
      RXS_JobLog( 'End Parsing.' );
  endsl;

For our address child object, we would like to concatenate the street address and the city and state before we print them to the job log. To accomplish this, we will use individual global fields to store the parsed address data, then write the entire formatted address to the job log during the object end event.

We’ll use the RXS_JSON_OBJECT event for the address child object to clear all of the global address component fields when the parser detects the beginning of a new address object:

when pType = RXS_JSON_OBJECT;
  select;
    when pPath = '[*]/address';
      clear streetNbr;
      clear streetName;
      clear aptNbr;
      clear city;
      clear state;
      clear zip;
   endsl;

After the address fields have been parsed, the RXS_JSON_OBJECT_END event will trigger for the address block, and we can perform further processing on the address data before writing it to the job log:

when pType = RXS_JSON_OBJECT_END;
  select;
    when pPath = '[*]/address';
      RXS_JobLog( 'Contact Address:' );
      RXS_JobLog( x'05' + streetNbr + ' ' + streetName );
      if %Len(aptNbr) > 0;
        RXS_JobLog( x'05' + 'Apt. ' + aptNbr );
      endif;
      RXS_JobLog( x'05' + city + ', ' + state );
      RXS_JobLog( x'05' + zip );
  endsl;

Note that we’re using the hexadecimal notation for the TAB character to indent the address lines – this is just for display purposes and is not required. We are also checking the length of the “aptNbr” field, so that we only write the line when an apartment number is present in the parsed data.

Because we are retrieving our JSON data in its equivalent RPG data type, we need to make selectors for each of our data types and a corresponding variable within our handler subprocedure for each non-string data type. For example, in order to retrieve the parsed data as a boolean, we need to declare an RPG boolean BASED variable:

D BoolVal         S               N   Based(pData)

Then we can use the following selector to capture a boolean variable event and retrieve the parsed data as an RPG indicator:

when pType = RXS_JSON_BOOLEAN;
  select;
    when pPath = '[*]/salesProspect';
      if BoolVal;
        RXS_JobLog( 'Sales Prospect?: Yes' );
      else;
        RXS_JobLog( 'Sales Prospect?: No' );
      endif;
  endsl;

We can retrieve integer values in a similar fashion, using a BASED variable like this:

D IntVal          S             20I 0 Based(pData)

Note that any integer values returned by the JSON parser will always be of this integer data type, regardless of the actual size of the number.

Our final pType selector will be for RXS_JSON_STRING events.

We’ll use RXS_STR() to retrieve the parsed data to an RPG character data variable, and either store the data for further processing or print the data to the job log. Here is an example where we retrieve the value of the “name” element and print the parsed data to the job log:

select;
  when pPath = '[*]/name';
    clear Val;
    Val = RXS_STR( pData : pDataLen );
    RXS_JobLog( 'Name: ' + Val ); 

Now that our parsing handler subprocedure is complete, we will write the main subprocedure. In order to initialize the JSON parsing engine, we need to configure the RXS_ParseJson() operation using an RXS_ParseJsonDS_t data structure. First, we will specify the procedure address for our JSON handler subprocedure:

ParseJsonDS.Handler = %Paddr( JsonHandler );

By default, the JSON parser retrieves parsed data as character (string) data. Because we want to parse our data in its native JSON data type, we will use the ConvertDataToString field to override the automatic conversion:

ParseJsonDS.ConvertDataToString = RXS_NO;

RXS_NO is a constant representing the RPG boolean value *Off. This setting allows the parsing engine to return different JSON data type events, which correspond to the RXS_JSON_* types referenced in our parsing handler. If this setting is left as the default value of RXS_YES (*On), every data event will be returned as an RXS_JSON_STRING event, regardless of the actual data type.

We will be parsing data from a file in the IFS, and we will use the Stmf field in our RXS_ParseJsonDS_t data structure to specify the location of our JSON document.

ParseJsonDS.Stmf = '/tmp/parseJsonDemo.json';

RXS_ParseJson() accepts two parameters, the first of which is a variable containing JSON data to be parsed. Because we are reading data from a file, we will omit the first parameter:

RXS_ParseJson( *Omit : ParseJsonDS );

The parser will now read through the JSON document and call our JsonHandler subprocedure for each event that it detects. If the handler subprocedure finds a select option for the corresponding event and type, it will process the retrieved data. Otherwise, the handler subprocedure will return control to the JSON parser, which will move on to the next event.

Following the code we wrote earlier, our handler subprocedure writes the contact information to the job log. Here is the output from our program:

screen-shot-2016-11-21-at-7-46-33-am

screen-shot-2016-11-21-at-7-47-00-am

screen-shot-2016-11-21-at-7-47-12-am

screen-shot-2016-11-21-at-7-47-23-am

While this example demonstrated parsing a simple IFS file that we created manually, the same process can be used to parse JSON data that was received as a request to or response from a web service.

Does your business need to handle JSON data on your IBM i? Are you building a JSON-based web service? Contact our sales team at sales@krengeltech.com to discuss how RPG-XML Suite can meet your JSON communication requirements!

Existing RXS customers with a paid maintenance contract are eligible to upgrade to RXS 3.3 for no additional cost – contact our support team at isupport@krengeltech.com for more information!

One Response

Leave a Reply

Your email address will not be published. Required fields are marked *