Calling a Web Service with JSON

The following example program demonstrates how to call a web service using RPG-XML Suite APIs. We will be transmitting and receiving JSON data, and we will be calling the web service with an HTTP POST request.

This tutorial calls the SmartyStreets API for US street address validation. This API leaverages the USPS postal database to return verified and corrected address data. The API allows up to 100 addresses to be submitted for validation in a single request, but for simplicity this tutorial demonstrates sending a single address.

Because the JSON composition and parsing APIs are only available starting with RPG-XML Suite version 3.3, this tutorial will not work with older versions - please contact our support team at isupport@krengeltech.com to help you upgrade.

Throughout this tutorial, we will reference code samples. Some lines may have been omitted in these samples, and you should reference the full program source code at the bottom of this page.

For more in-depth information about JSON composition and parsing with RPG-XML Suite, please review these blog posts: Composing JSON with RPG-XML Suite 3.3 Parsing JSON with RPG-XML Suite 3.3

Here are the APIs we will be using in this tutorial:

API Name   Description
RXS_ResetDS()   Resets and initializes RXS datastructures.
RXS_CreateJson()   Configures and initializes JSON composition.
RXS_ComposeJsonObject()   Composes a child JSON object.
RXS_ComposeJsonString()   Composes a string value in a JSON object or array.
RXS_ComposeJsonNumber()   Composes an integer or decimal value in a JSON object or array.
RXS_GetJsonString()   Retrieves the composed JSON string.
RXS_Transmit()   Sends an HTTP request to the web service.
RXS_ParseJson()   Parses the JSON response from the web service.
RXS_DestroyJson()   Performs cleanup operations after any JSON composition or parsing.
     

There are three main parts to a program that calls a web service:

  1. Composing - using the JSON composition APIs to compose the request JSON.
  2. Transmitting - sending the HTTP request to the web service.
  3. Parsing - retrieving data from the response JSON.

Our program was written as a service program, with a subprocedure exported for use by other programs. This same functionality and program layout could be used in a program mainline, subroutine, or internally defined subprocedure.

Composing

First we need to examine the JSON structure that SmartyStreets is expecting to receive:

[
  {
    "street":"1 Santa Claus",
    "city":"North Pole",
    "state":"AK",
    "candidates":10
  },
  {
    "addressee":"Apple Inc",
    "street":"1 infinite loop",
    "city":"cupertino",
    "state":"CA",
    "zipcode":"95014",
    "candidates":10
  }
]

Note that this JSON has a root array structure - indicated by the square brackets [] - because SmartyStreets can accept multiple address objects in a single JSON request. Even though we are only sending a single address to be validated, we need to maintain this root array structure.

First, we need to configure and initialize the JSON composition. This is achieved with CreateJsonDS and RXS_CreateJson(). To configure the root element as an array, we initialize JSON composition with the RXS_JSON_STRUCTURE_ARRAY type:


CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_ARRAY;
RootDS = RXS_CreateJson( CreateJsonDS );

RootDS represents the base array of our JSON request. We will create a child JSON object next:


AddressDS = RXS_ComposeJsonObject( *Omit : RootDS );

This child object will contain the data for our address to be validated. Next, we will compose the address fields to the child AddressDS structure, using the appropriate RXS_ComposeJson[Type]() APIs:


if pRequestDS.input_id <> *Blanks;
  RXS_ComposeJsonString( 'input_id' : %Trim( pRequestDS.input_id ) : AddressDS );
endif;

if pRequestDS.street <> *Blanks;
  RXS_ComposeJsonString( 'street' : %Trim( pRequestDS.street ) : AddressDS );
endif;

<...>

RXS_ComposeJsonNumber( 'candidates' : %Trim( %Char( pRequestDS.candidates ) ) : AddressDS );

if pRequestDS.match <> *Blanks;
  RXS_ComposeJsonString( 'match' : %Trim( pRequestDS.match ) : AddressDS );
endif;

Once we have written the address data to the AddressDS object, we will retrieve the composed JSON string with RXS_GetJsonString():


RequestData = RXS_GetJsonString( CreateJsonDS );

RequestData now contains this JSON structure:

[
  {
    "street":"1 infinite loop",
    "city":"cupertino",
    "state":"CA",
    "zipcode":"95014",
    "addressee":"Apple Inc",
    "candidates":10
  }
]

Transmitting

Now that we have our composed JSON request, we will make an HTTP call to the SmartyStreets API. Set the URI for the web service, the HTTP method - POST - and the content-type as demonstrated below. We are also setting a LogFile for debugging purposes - with this value set, RPG-XML Suite will store a log of the request and response data and headers when we call the service.


TransmitDS.Uri = 'https://us-street.api.smartystreets.com/street-address'
               + '?auth-id=' + %Trim( pRequestDS.AuthId )
               + '&auth-token=' + %Trim( pRequestDS.AuthToken );
TransmitDS.HTTPMethod = RXS_HTTP_METHOD_POST;
TransmitDS.LogFile = %Trim( pRequestDS.LogFile );
TransmitDS.HeaderContentType = 'application/json';

And then we will call the web service:


ResponseData = RXS_Transmit( RequestData : TransmitDS );

ResponseData now contains the validated address data returned by the SmartyStreets API. We use a simple error check to make sure that the HTTP status code was 200 – which indicates a successful request – and move on to parsing.


if TransmitDS.HTTPResponse.StatusCode <> 200;
  // error handling, don't parse
  exsr error;
endif;

Parsing

When parsing JSON data, you first need to build a parsing handler subprocedure. This handler subprocedure is called by the RPG-XML Suite parsing API, and it determines what to do with data retrieved from the JSON object. All JSON parsing handlers have this prototype:


Dcl-Pi *N Ind;
  pType Int(5) Const;
  pPath Like(RXS_Var64Kv_t) Const;
  pIndex Uns(10) Const;
  pData Pointer Const;
  pDataLen Uns(10) Const;
End-Pi;

The general structure of a parsing handler will be a select; block that checks target paths in the JSON object and retrieves the data into a field:


select;
  when pPath = '[*]/input_id';
    exsr getData;
    gResponseDS.input_id = ParsedData;

  when pPath = '[*]/input_index';
    exsr getData;
    gResponseDS.input_index = %Uns( ParsedData );

  when pPath = '[*]/candidate_index';
    exsr getData;
    gResponseDS.candidate_index = %Uns( ParsedData );
    
  etc.

The [*] notation represents an array, either a root array or a child array. Since our response JSON is a root array, we will see this notation at the start of each of our paths in the parsing handler. Make a when condition for each JSON path that contains data you want to retrieve. We are saving this data to a global response data structure, but you could also use the handler to write this data out to a physical file. Our global response data structure will be returned to the calling program once parsing completes.

To configure the parsing operation, we need to provide the %Paddr for the parsing handler subprocedure. We will then call RXS_ParseJson(), which parses the JSON document with our handler subprocedure, retrieves the data, then passes the data back through to the calling program in the pResponseDS parameter.


ParseJsonDS.Handler = %Paddr( StreetAddressHandler );
reset gResponseDS;
RXS_ParseJson( ResponseData : ParseJsonDS );
eval-corr pResponseDS = gResponseDS;

Example Program

Because we built a service program for the SmartyStreets API, we will need a main program to call the exported subprocedure. Here is a simple example program that calls our SMRT_StreetAddress() subprocedure and writes response information to the job log:


**FREE

Ctl-Opt ActGrp(*Caller) BndDir('RXSBND':'SMRTBND');

/COPY QRPGLECPY,RXSCB
/COPY QRPGLECPY,SMRTCB

// This is included for demo output purposes.
Dcl-Pr WriteToJobLog Int(10) Extproc('Qp0zLprintf');
  pString Pointer Value Options(*String);
End-Pr;
Dcl-C NewLine x'15';

Dcl-Ds RequestDS LikeDS(SMRT_StreetAddressRequestDS_t) Inz(*LikeDS);
Dcl-Ds ResponseDS LikeDS(SMRT_StreetAddressResponseDS_t) Inz(*LikeDS);
Dcl-Ds ErrorDS LikeDS(SMRT_ErrorDS_t) Inz(*LikeDS);

reset RequestDS;
reset ResponseDS;
reset ErrorDS;

RequestDS.addressee = 'Apple Inc';
RequestDS.street = '1 infinite loop';
RequestDS.city = 'cupertino';
RequestDS.state = 'CA';
RequestDS.zipcode = '95014';
RequestDS.candidates = 10;
RequestDS.LogFile = '/tmp/smartystreets.txt';
RequestDS.AuthId = '050...988';
RequestDS.AuthToken = 'yQw...YMh';

if not SMRT_StreetAddress( RequestDS : ResponseDS : ErrorDS );
  // error handling
  WriteToJobLog( ErrorDS.ErrorMessage );
else;
  // address validation response
  WriteToJobLog( 'Validated address: ' + NewLine );
  WriteToJobLog( ResponseDS.delivery_line_1 + ' ' + ResponseDS.delivery_line_2 + NewLine );
  WriteToJobLog( ResponseDS.last_line + NewLine );
  WriteToJobLog( 'Delivery Point Validation: ' + ResponseDS.dpv_match_code + NewLine );
  WriteToJobLog( 'DPV Notes: ' + ResponseDS.dpv_footnotes + NewLine );
  WriteToJobLog( 'Active? ' + ResponseDS.active + NewLine );
  WriteToJobLog( 'Record Type: ' + ResponseDS.record_type + NewLine );
endif;

return;

If the address validation was successful, your job log output should look something like this:

Full Service Program Code


    **FREE
Ctl-Opt NoMain;

/COPY QRPGLECPY,RXSCB
/COPY QRPGLECPY,SMRTCB

Dcl-Ds gResponseDS LikeDS(SMRT_StreetAddressResponseDS_t) Inz(*LikeDS);

Dcl-Proc SMRT_StreetAddress Export;
  Dcl-Pi *N Ind;
    pRequestDS Const LikeDS(SMRT_StreetAddressRequestDS_t);
    pResponseDS LikeDS(SMRT_StreetAddressResponseDS_t);
    pErrorDS LikeDS(SMRT_ErrorDS_t);
  End-Pi;

  Dcl-Ds TransmitDS LikeDS(RXS_TransmitDS_t);
  Dcl-Ds CreateJsonDS LikeDS(RXS_CreateJsonDS_t);
  Dcl-Ds RootDS LikeDS(RXS_JsonStructureDS_t);
  Dcl-Ds AddressDS LikeDS(RXS_JsonStructureDS_t);
  Dcl-Ds ParseJsonDS LikeDS(RXS_ParseJsonDS_t);

  Dcl-S RequestData Like(RXS_Var32Kv_t);
  Dcl-S ResponseData Like(RXS_Var64Kv_t);

  exsr compose;

  exsr transmit;

  exsr parse;

  RXS_DestroyJson( CreateJsonDS );

  return *On;


  begsr compose;
    RXS_RESETDS( CreateJsonDS : RXS_DS_TYPE_CREATEJSON );
    RXS_RESETDS( RootDS : RXS_DS_TYPE_JSONSTRUCTURE );
    RXS_RESETDS( AddressDS : RXS_DS_TYPE_JSONSTRUCTURE );

    CreateJsonDS.JsonStructureType = RXS_JSON_STRUCTURE_ARRAY;
    RootDS = RXS_CreateJson( CreateJsonDS );

    // This API can be sent up to 100 addresses at a time, but this
    //  code is only going to focus on single address validation.

    AddressDS = RXS_ComposeJsonObject( *Omit : RootDS );

    if pRequestDS.input_id <> *Blanks;
      RXS_ComposeJsonString( 'input_id' : %Trim( pRequestDS.input_id ) : AddressDS );
    endif;

    if pRequestDS.street <> *Blanks;
      RXS_ComposeJsonString( 'street' : %Trim( pRequestDS.street ) : AddressDS );
    endif;

    if pRequestDS.street2 <> *Blanks;
      RXS_ComposeJsonString( 'street2' : %Trim( pRequestDS.street2 ) : AddressDS );
    endif;

    if pRequestDS.secondary <> *Blanks;
      RXS_ComposeJsonString( 'secondary' : %Trim( pRequestDS.secondary ) : AddressDS );
    endif;

    if pRequestDS.city <> *Blanks;
      RXS_ComposeJsonString( 'city' : %Trim( pRequestDS.city ) : AddressDS );
    endif;

    if pRequestDS.state <> *Blanks;
      RXS_ComposeJsonString( 'state' : %Trim( pRequestDS.state ) : AddressDS );
    endif;

    if pRequestDS.zipcode <> *Blanks;
      RXS_ComposeJsonString( 'zipcode' : %Trim( pRequestDS.zipcode ) : AddressDS );
    endif;

    if pRequestDS.lastline <> *Blanks;
      RXS_ComposeJsonString( 'lastline' : %Trim( pRequestDS.lastline ) : AddressDS );
    endif;

    if pRequestDS.addressee <> *Blanks;
      RXS_ComposeJsonString( 'addressee' : %Trim( pRequestDS.addressee ) : AddressDS );
    endif;

    if pRequestDS.urbanization <> *Blanks;
      RXS_ComposeJsonString( 'urbanization' : %Trim( pRequestDS.urbanization ) : AddressDS );
    endif;

    RXS_ComposeJsonNumber( 'candidates' : %Trim( %Char( pRequestDS.candidates ) ) : AddressDS );

    if pRequestDS.match <> *Blanks;
      RXS_ComposeJsonString( 'match' : %Trim( pRequestDS.match ) : AddressDS );
    endif;

    CreateJsonDS.Prettify = RXS_YES;
    RequestData = RXS_GetJsonString( CreateJsonDS );
  endsr;

  begsr transmit;
    RXS_RESETDS( TransmitDS : RXS_DS_TYPE_TRANSMIT );
    TransmitDS.Uri = 'https://us-street.api.smartystreets.com/street-address'
                   + '?auth-id=' + %Trim( pRequestDS.AuthId )
                   + '&auth-token=' + %Trim( pRequestDS.AuthToken );
    TransmitDS.HTTPMethod = RXS_HTTP_METHOD_POST;
    TransmitDS.LogFile = %Trim( pRequestDS.LogFile );
    TransmitDS.HeaderContentType = 'application/json';

    ResponseData = RXS_Transmit( RequestData : TransmitDS );

    if TransmitDS.HTTPResponse.StatusCode <> 200;
      // error handling, don't parse
      exsr error;
    endif;

  endsr;

  begsr parse;
    RXS_RESETDS( ParseJsonDS : RXS_DS_TYPE_PARSEJSON );
    ParseJsonDS.Handler = %Paddr( StreetAddressHandler );
    reset gResponseDS;
    RXS_ParseJson( ResponseData : ParseJsonDS );
    eval-corr pResponseDS = gResponseDS;
  endsr;

  begsr error;
    if TransmitDS.HTTPResponse.StatusCode > 0;
      pErrorDS.ErrorMessage = 'HTTP Error ' + %Char( TransmitDS.HTTPResponse.StatusCode );
    else;
      pErrorDS.ErrorMessage = 'An unknown error occurred - address was not validated';
    endif;

    RXS_DestroyJson( CreateJsonDS );
    return *Off;
  endsr;
End-Proc;

Dcl-Proc StreetAddressHandler;
  Dcl-Pi *N Ind;
    pType Int(5) Const;
    pPath Like(RXS_Var64Kv_t) Const;
    pIndex Uns(10) Const;
    pData Pointer Const;
    pDataLen Uns(10) Const;
  End-Pi;

  Dcl-S ParsedData Like(RXS_Var1Kv_t);

  select;
    when pPath = '[*]/input_id';
      exsr getData;
      gResponseDS.input_id = ParsedData;

    when pPath = '[*]/input_index';
      exsr getData;
      gResponseDS.input_index = %Uns( ParsedData );

    when pPath = '[*]/candidate_index';
      exsr getData;
      gResponseDS.candidate_index = %Uns( ParsedData );

    when pPath = '[*]/addressee';
      exsr getData;
      gResponseDS.addressee = ParsedData;

    when pPath = '[*]/delivery_line_1';
      exsr getData;
      gResponseDS.delivery_line_1 = ParsedData;

    when pPath = '[*]/delivery_line_2';
      exsr getData;
      gResponseDS.delivery_line_2 = ParsedData;

    when pPath = '[*]/last_line';
      exsr getData;
      gResponseDS.last_line = ParsedData;

    when pPath = '[*]/delivery_point_barcode';
      exsr getData;
      gResponseDS.delivery_point_barcode = ParsedData;

    when pPath = '[*]/urbanization';
      exsr getData;
      gResponseDS.urbanization = ParsedData;

    when pPath = '[*]/components/primary_number';
      exsr getData;
      gResponseDS.primary_number = ParsedData;

    when pPath = '[*]/components/street_name';
      exsr getData;
      gResponseDS.street_name = ParsedData;

    when pPath = '[*]/components/street_predirection';
      exsr getData;
      gResponseDS.street_predirection = ParsedData;

    when pPath = '[*]/components/street_postdirection';
      exsr getData;
      gResponseDS.street_postdirection = ParsedData;

    when pPath = '[*]/components/street_suffix';
      exsr getData;
      gResponseDS.street_suffix = ParsedData;

    when pPath = '[*]/components/secondary_number';
      exsr getData;
      gResponseDS.secondary_number = ParsedData;

    when pPath = '[*]/components/secondary_designator';
      exsr getData;
      gResponseDS.secondary_designator = ParsedData;

    when pPath = '[*]/components/extra_secondary_number';
      exsr getData;
      gResponseDS.extra_secondary_number = ParsedData;

    when pPath = '[*]/components/extra_secondary_designator';
      exsr getData;
      gResponseDS.extra_secondary_designator = ParsedData;

    when pPath = '[*]/components/pmb_designator';
      exsr getData;
      gResponseDS.pmb_designator = ParsedData;

    when pPath = '[*]/components/pmb_number';
      exsr getData;
      gResponseDS.pmb_number = ParsedData;

    when pPath = '[*]/components/city_name';
      exsr getData;
      gResponseDS.city_name = ParsedData;

    when pPath = '[*]/components/default_city_name';
      exsr getData;
      gResponseDS.default_city_name = ParsedData;

    when pPath = '[*]/components/state_abbreviation';
      exsr getData;
      gResponseDS.state_abbreviation = ParsedData;

    when pPath = '[*]/components/zipcode';
      exsr getData;
      gResponseDS.zipcode = ParsedData;

    when pPath = '[*]/components/plus4_code';
      exsr getData;
      gResponseDS.plus4_code = ParsedData;

    when pPath = '[*]/components/delivery_point';
      exsr getData;
      gResponseDS.delivery_point = ParsedData;

    when pPath = '[*]/components/delivery_point_check_digit';
      exsr getData;
      gResponseDS.delivery_point_check_digit = ParsedData;

    when pPath = '[*]/metadata/record_type';
      exsr getData;
      gResponseDS.record_type = ParsedData;

    when pPath = '[*]/metadata/zip_type';
      exsr getData;
      gResponseDS.zip_type = ParsedData;

    when pPath = '[*]/metadata/county_fips';
      exsr getData;
      gResponseDS.county_fips = ParsedData;

    when pPath = '[*]/metadata/county_name';
      exsr getData;
      gResponseDS.county_name = ParsedData;

    when pPath = '[*]/metadata/carrier_route';
      exsr getData;
      gResponseDS.carrier_route = ParsedData;

    when pPath = '[*]/metadata/congressional_district';
      exsr getData;
      gResponseDS.congressional_district = ParsedData;

    when pPath = '[*]/metadata/building_default_indicator';
      exsr getData;
      gResponseDS.building_default_indicator = ParsedData;

    when pPath = '[*]/metadata/rdi';
      exsr getData;
      gResponseDS.rdi = ParsedData;

    when pPath = '[*]/metadata/elot_sequence';
      exsr getData;
      gResponseDS.elot_sequence = ParsedData;

    when pPath = '[*]/metadata/elot_sort';
      exsr getData;
      gResponseDS.elot_sort = ParsedData;

    when pPath = '[*]/metadata/latitude';
      exsr getData;
      gResponseDS.latitude = %Dec( ParsedData : 12 : 9 );

    when pPath = '[*]/metadata/longitude';
      exsr getData;
      gResponseDS.longitude = %Dec( ParsedData : 12 : 9 );

    when pPath = '[*]/metadata/precision';
      exsr getData;
      gResponseDS.precision = ParsedData;
    when pPath = '[*]/metadata/time_zone';
      exsr getData;
      gResponseDS.time_zone = ParsedData;

    when pPath = '[*]/metadata/utc_offset';
      exsr getData;
      gResponseDS.utc_offset = %Dec( ParsedData : 4 : 2 );

    when pPath = '[*]/metadata/dst';
      exsr getData;
      gResponseDS.dst = ParsedData;

    when pPath = '[*]/analysis/dpv_match_code';
      exsr getData;
      gResponseDS.dpv_match_code = ParsedData;

    when pPath = '[*]/analysis/dpv_footnotes';
      exsr getData;
      gResponseDS.dpv_footnotes = ParsedData;

    when pPath = '[*]/analysis/dpv_cmra';
      exsr getData;
      gResponseDS.dpv_cmra = ParsedData;

    when pPath = '[*]/analysis/dpv_vacant';
      exsr getData;
      gResponseDS.dpv_vacant = ParsedData;

    when pPath = '[*]/analysis/active';
      exsr getData;
      gResponseDS.active = ParsedData;

    when pPath = '[*]/analysis/ews_match';
      exsr getData;
      gResponseDS.ews_match = ParsedData;

    when pPath = '[*]/analysis/footnotes';
      exsr getData;
      gResponseDS.footnotes = ParsedData;

    when pPath = '[*]/analysis/lacslink_code';
      exsr getData;
      gResponseDS.lacslink_code = ParsedData;

    when pPath = '[*]/analysis/lacslink_indicator';
      exsr getData;
      gResponseDS.lacslink_indicator = ParsedData;

    when pPath = '[*]/analysis/suitelink_match';
      exsr getData;
      gResponseDS.suitelink_match = ParsedData;

  endsl;

  return *On;


  begsr getData;
    if pData <> *Null and pDataLen > 0;
      ParsedData = RXS_STR( pData : pDataLen );
    endif;
  endsr;
End-Proc;
    

    **FREE
/IF DEFINED(SMRTCB)
/EOF
/ENDIF
/DEFINE SMRTCB

Dcl-Pr SMRT_StreetAddress Ind Extproc(*DclCase);
  RequestDS Const LikeDS(SMRT_StreetAddressRequestDS_t);
  ResponseDS LikeDS(SMRT_StreetAddressResponseDS_t);
  ErrorDS LikeDS(SMRT_ErrorDS_t);
End-Pr;

Dcl-Ds SMRT_StreetAddressRequestDS_t Qualified Template Inz;
  input_id VarChar(36);
  street VarChar(64);
  street2 VarChar(64);
  secondary VarChar(64);
  city VarChar(64);
  state VarChar(32);
  zipcode VarChar(16);
  lastline VarChar(64);
  addressee VarChar(64);
  urbanization VarChar(64);
  candidates Uns(3);
  match VarChar(8);
  AuthId VarChar(36);
  AuthToken VarChar(20);
  LogFile Like(RXS_Var8Kv_t);
End-Ds;

Dcl-Ds SMRT_StreetAddressResponseDS_t Qualified Template Inz;
  input_id VarChar(36);
  input_index Uns(3);
  candidate_index Uns(3);
  addressee VarChar(50);
  delivery_line_1 VarChar(50);
  delivery_line_2 VarChar(50);
  last_line VarChar(50);
  delivery_point_barcode VarChar(12);
  // Components ------------------------
  urbanization VarChar(64);
  primary_number VarChar(30);
  street_name VarChar(30);
  street_predirection VarChar(16);
  street_postdirection VarChar(16);
  street_suffix VarChar(16);
  secondary_number VarChar(32);
  secondary_designator VarChar(16);
  extra_secondary_number VarChar(32);
  extra_secondary_designator VarChar(16);
  pmb_designator VarChar(16);
  pmb_number VarChar(16);
  city_name VarChar(64);
  default_city_name VarChar(64);
  state_abbreviation VarChar(2);
  zipcode VarChar(5);
  plus4_code VarChar(4);
  delivery_point VarChar(2);
  delivery_point_check_digit VarChar(1);
  // Metadata --------------------------
  record_type VarChar(1);
  zip_type VarChar(32);
  county_fips VarChar(5);
  county_name VarChar(64);
  carrier_route VarChar(4);
  congressional_district VarChar(2);
  building_default_indicator VarChar(1);
  rdi VarChar(12);
  elot_sequence VarChar(4);
  elot_sort VarChar(4);
  latitude Packed(12:9);
  longitude Packed(12:9);
  precision VarChar(18);
  time_zone VarChar(48);
  utc_offset Packed(4:2);
  dst VarChar(5);
  // Analysis --------------------------
  dpv_match_code VarChar(1);
  dpv_footnotes VarChar(32);
  dpv_cmra VarChar(1);
  dpv_vacant VarChar(1);
  active VarChar(1);
  ews_match VarChar(5);
  footnotes VarChar(12);
  lacslink_code VarChar(2);
  lacslink_indicator VarChar(1);
  suitelink_match VarChar(5);
End-Ds;

Dcl-Ds SMRT_ErrorDS_t Qualified Template Inz;
  ErrorMessage VarChar(1024);
End-Ds;

Dcl-C SMRT_MATCH_TYPE_STRICT 'strict';
Dcl-C SMRT_MATCH_TYPE_RANGE 'range';
Dcl-C SMRT_MATCH_TYPE_INVALID 'invalid';