01 - Programmazione01a - RPG05b - XML e JSON

JSON e IBM i – FAQ & Howto (IT)

Last Updated on 8 Maggio 2020 by Roberto De Pedrini

Questa non vuole essere una guida completa per la gestione dei JSON con IBM i – AS400 ma semplicemente una raccolta di FAQ, trucchi e tecniche utilizzate nell’esperienza quotidiana oppure “rubate” da qualche sito internet o forum.

Vuole essere una guida in continua evoluzione e per questo invito anche voi a farla crescere utilizzando i commenti all’articolo o segnalandoci le vostre esperienze.

iJSON-FAQ-001: Il nuovo comando YAJLGEN di Scott Klement

Sempre lui, sempre il grande Scott Klement dopo averci regalato YAJL e poi YAJL-INTO per la gestione dei JSON nei nostri programmi RPG arriva con un fantastcio comando YAJLGEN, compreso nel prodotto Open Source YAJL: https://www.scottklement.com/yajl/

Con YAJLGEN è possibile costruire la DS per la funzione DATA-INTO di RPG partendo da un qualsia JSON salvato nell’IFS. Non è fantastico? Prendo un JSON, lancio YAJLGEN che mi crea la DS per RPG e un template di chiamata alla funzione DATA-INTO e il lavoro è fatto …. da lì in avanti ho la mia DS compilata e faccio ciò che voglio dei dati letti dal documento JSON.

Vediamo un esempio: prendo un JSON anche abbastanza complesso, con diverse nidificazioni (preso dal sito opensource di Adobe) :

{"root":[
     {
         "id": "0001",
         "type": "donut",
         "name": "Cake",
         "ppu": 0.55,
         "batters":
             {
                 "batter":
                     [
                         { "id": "1001", "type": "Regular" },
                         { "id": "1002", "type": "Chocolate" },
                         { "id": "1003", "type": "Blueberry" },
                         { "id": "1004", "type": "Devil's Food" }
                     ]
             },
         "topping":
             [
                 { "id": "5001", "type": "None" },
                 { "id": "5002", "type": "Glazed" },
                 { "id": "5005", "type": "Sugar" },
                 { "id": "5007", "type": "Powdered Sugar" },
                 { "id": "5006", "type": "Chocolate with Sprinkles" },
                 { "id": "5003", "type": "Chocolate" },
                 { "id": "5004", "type": "Maple" }
             ]
     },
     {
         "id": "0002",
         "type": "donut",
         "name": "Raised",
         "ppu": 0.55,
         "batters":
             {
                 "batter":
                     [
                         { "id": "1001", "type": "Regular" }
                     ]
             },
         "topping":
             [
                 { "id": "5001", "type": "None" },
                 { "id": "5002", "type": "Glazed" },
                 { "id": "5005", "type": "Sugar" },
                 { "id": "5003", "type": "Chocolate" },
                 { "id": "5004", "type": "Maple" }
             ]
     },
     {
         "id": "0003",
         "type": "donut",
         "name": "Old Fashioned",
         "ppu": 0.55,
         "batters":
             {
                 "batter":
                     [
                         { "id": "1001", "type": "Regular" },
                         { "id": "1002", "type": "Chocolate" }
                     ]
             },
         "topping":
             [
                 { "id": "5001", "type": "None" },
                 { "id": "5002", "type": "Glazed" },
                 { "id": "5003", "type": "Chocolate" },
                 { "id": "5004", "type": "Maple" }
             ]
     }
 ]
 }

Ho salvago il documento JSON nell’IFS del mio IBM i … quindi ho chiamato la funzione YAJLGEN:

YAJLGEN STMF('/myjson/jsonexample3.json') SRCFILE(FAQ400/SRC) SRC MBR(JSONEXAM)                                                                   

Ecco il mio sorgente JSONEXAM creato dalla funzione YAJLGEN: compilo, metto in debug … fantastico! … la mia DS bella e compilata!

   ctl-opt dftactgrp(*no);

   readTheJson();
   *inlr = *on;

   // FIXME:
   //   - The field lengths (varchar/packed) are guesses
   //       and should be adjusted based on your business rules.
   //   - The array lengths (dim keywords) are also guesses
   //       and should be adjusted based on your business rules

   dcl-proc readTheJson;

     dcl-ds jsonDoc qualified;
       num_ROOT int(10) inz(0);
       dcl-ds ROOT dim(100);
         ID varchar(4) inz('');
         TYPE varchar(5) inz('');
         NAME varchar(4) inz('');
         PPU packed(3: 2) inz(0);
         dcl-ds BATTERS;
           num_BATTER int(10) inz(0);
           dcl-ds BATTER dim(4);
             ID varchar(4) inz('');
             TYPE varchar(30) inz('');
           end-ds;
         end-ds;
         num_TOPPING int(10) inz(0);
         dcl-ds TOPPING dim(7);
           ID varchar(4) inz('');
           TYPE varchar(4) inz('');
         end-ds;
       end-ds;
     end-ds;

     dcl-s ifsPathName varchar(5000);

     ifsPathName = '/myjson/jsonexample3.json';

     data-into jsonDoc %DATA( ifsPathname
                            : 'doc=file case=convert countprefix=num_')
                     %PARSER( 'YAJLINTO'
                            : '{ "document_name": "jsonDoc", +
                                 "number_prefix": "YAJL_" }');

   end-proc; 

Alcune attenzioni:

  • come viene indicato nelle note FIXME di Scott Klement la dimensione degli array e dei campi alfabetici delle DS va sistemato secondo le vostr esigenze … nel caso sopra indicato ho modificato la dimensione dell’array “root” della variabule “TYPE”
  • Il JSON preso dal sito di Adobe non aveva una “root” … partiva direttamente come array con una parentesi quadra “[” e non una graffa “{” … ho dovuto aggiungere una chiave-valore “root”
  • Probabilmente per un probelma di CCSID non corretto YAJLGEN porta dei caratteri strani nei parametri del %PARSER della specifica DATA-INTO … che ho dovuto modificare con parentesi graffa aperta e chiusa … ma con il tempo risparmiato dall’utility ne è valsa sicuramente la pena!

iJSON-FAQ-002: Come aggiorno YAJL di Scott Klement

Purtroppo fino ad oggi, Agosto 2019, Scott non ha previsto un vero e proprio versioning dell’ottima libreria YAJL ( https://www.scottklement.com/yajl/ ) per la gestione dei JSON in RPG … che invece tiene regolarmente aggiornato con nuove funzionalità. Come suggerito anche dallo stesso Scott Klement, per vedere la versione fare un:

DSPSRVPGM SRVPGM(YAJL)

La versione ad oggi, Agosto 2019, disponibile è stata compilata il 19/07/2019

Se avete una versione più vecchia l’aggiornamento lo si può fare “passando tranquillamente sopra” la vecchia libreria … quindi, dopo aver scaricato il SAVF e portanto dentro l’IFS seguendo le istruzione del Readme.txt di Scott … eseguite il comando RSTLIB con una opzione in più:

 RSTOBJ OBJ(*ALL) SAVLIB(QTEMP) DEV(*SAVF) RSTLIB(YAJL) SAVF(QGPL/YAJLLIB72) ALWOBJDIF(*ALL)  

iJSON-FAQ-003: JSON con diversi Arrays, JSON_TABLE o DATA-INTO?

Quando abbiamo a che fare con un JSON con diversi livelli di Arrays non è sempre semplicissimo farne un parsing corretto.

Prendiamo questo esempio di JSON trovato sul forum Midrange.com: un JSON con i dati nidificati per le distanze di 4 differenti indirizzi da un punto, sembra un ritorno da Google Maps o qualche altro servizio simile di geo-localizzazione:

{
    "destination_addresses" : [
       "1234 Main St, La Crescent, MN 55947, USA",
       "1742 Park Ave, La Crosse, WI 54601, USA",
       "1210 S 10th St, La Crosse, WI 54601, USA",
       "9876 W 52nd Ave, Onalaska, WI 854650 USA"
    ],
    "origin_addresses" : [ "1500 Old Hickory Dr, La Crescent, MN 55947, USA" ],
    "rows" : [
       {
          "elements" : [
             {
                "distance" : {
                   "text" : "18.3 mi",
                   "value" : 29452
                },
                "duration" : {
                   "text" : "29 mins",
                   "value" : 1746
                },
                "status" : "OK"
             },
             {
                "distance" : {
                   "text" : "9.5 mi",
                   "value" : 15308
                },
                "duration" : {
                   "text" : "18 mins",
                   "value" : 1056
                },
                "status" : "OK"
             },
             {
                "distance" : {
                   "text" : "3.8 mi",
                   "value" : 6142
                },
                "duration" : {
                   "text" : "9 mins",
                   "value" : 534
                },
                "status" : "OK"
             },
             {
                "distance" : {
                   "text" : "19.3 mi",
                   "value" : 31032
                },
                "duration" : {
                   "text" : "22 mins",
                   "value" : 1331
                },
                "status" : "OK"
             }
          ]
       }
    ],
    "status" : "OK"
 }

Se il file è caricato sul nostro IFS (esempio ‘/home/FAQ00/gmaps.json’ in una specifica directory possiamo farne il parsing con JSON_TABLE e qualche JOIN/CROSS JOIN:

select * from
 ( 
 SELECT *
             from
                 JSON_TABLE(get_clob_from_file('/home/FAQ00/gmaps.json'),
                 '$'  COLUMNS(
                 origin_addresses VARCHAR(30)  PATH '$.origin_addresses'
                  ) ) as x 
 ) cc
 cross join table
 (
 SELECT  ROW_NUMBER() OVER () rownumber, a.*
             from
                 JSON_TABLE(get_clob_from_file('/home/FAQ00/gmaps.json'),
                 '$.rows.elements'  COLUMNS(
                  distance_text VARCHAR(30)  PATH '$.distance.text',
                  distance_value VARCHAR(30)  PATH '$.distance.value',
                  duration_text VARCHAR(30)  PATH '$.duration.text',
                  duration_value VARCHAR(30)  PATH '$.duration.value',
                  statust VARCHAR(30)  PATH '$.status'
              ) ) as  a 
 ) aa
 join table 
 (
 SELECT ROW_NUMBER() OVER () rownumber, b.*
             from
                 JSON_TABLE(get_clob_from_file('/home/FAQ00/gmaps.json'),
                 '$.destination_addresses'  COLUMNS(
                  Destination_addressess VARCHAR(30)  PATH '$'
                  ) ) as b 
 ) bb 
 on aa.rownumber=bb.rownumber 
              ;                                

Oppure, sfruttando la YAJLGEN di Scott Klement, creare un piccolo source RPG che con una DS e un DATA-INTO trasforma questo JSON in una normale DS nestata utilizzabile direttamente in RPG:

   ctl-opt dftactgrp(*no);

   readTheJson();
   *inlr = *on;

   // FIXME:
   //   - The field lengths (varchar/packed) are guesses
   //       and should be adjusted based on your business rules.
   //   - The array lengths (dim keywords) are also guesses
   //       and should be adjusted based on your business rules

   dcl-proc readTheJson;

     dcl-ds jsonDoc qualified;
       num_DESTINATION_ADDRESSES int(10) inz(0);
       DESTINATION_ADDRESSES varchar(40) inz('') dim(4);
       num_ORIGIN_ADDRESSES int(10) inz(0);
       ORIGIN_ADDRESSES varchar(47) inz('') dim(1);
       num_ROWS int(10) inz(0);
       dcl-ds ROWS dim(1);
         num_ELEMENTS int(10) inz(0);
         dcl-ds ELEMENTS dim(4);
           dcl-ds DISTANCE;
             TEXT varchar(7) inz('');
             VALUE packed(5) inz(0);
           end-ds;
           dcl-ds DURATION;
             TEXT varchar(7) inz('');
             VALUE packed(4) inz(0);
           end-ds;
           STATUS varchar(2) inz('');
         end-ds;
       end-ds;
       STATUS varchar(2) inz('');
     end-ds;

     dcl-s ifsPathName varchar(5000);

     ifsPathName = '/smedoc/UTE/DEPE/gmaps.json';

     data-into jsonDoc %DATA( ifsPathname
                            : 'doc=file case=convert countprefix=num_')
                     %PARSER( 'YAJLINTO'
                            : '{ "document_name": "jsonDoc", +
                                 "number_prefix": "YAJL_" }');

   end-proc;               

iJSON-FAQ-004: Quando un web-service torna un JSON non “normalizzato”

In alcuni casi potremmo avere a che fare con dei web services che tornano dei JSON non proprio nella forma normale:

 { key : value } 

ma in una forma “array”

 [{ key : value }, { key : value }, ...}]

In questo caso dobbiamo “ricostruire” il documento JSON prima di farlo elaborare da JSON_TABLE o altri servizi per JSONx

C’è un ottimo post di Scott Forstie dal titolo “JSON_TABLE and survival tips for shredding JSON with SQL che spiega bene come fare.

iJSON-FAQ-005: Esempi di JSON e ILE RPG / SQL by Tim Fathers

In questo repository di Git troviamo ottimi esempi di JSON e ILE RPG / SQL gentilmente messi a disposizione da Tim Fathers

Json-RPG-Example

iJSON-FAQ-006: JSON in una variabile char (con YAJL)

Se costruiamo un JSON con le procedure della YAJL di Scott Klement possiamo ricavare il documento JSON costruito anche in una variabile tramite la chiamata delle procedura yajl_copyBufStr()

dcl-s jsonStr char(1024);

yajl_beginObj();
.
.
yajl_endObj();
jsonStr = yajl_copyBufStr();
yajl_genClose();

Se in realtà ci serve il JSON per chiamare un Web Service utilizzando le funzioni di HTTPAPI sempre di Scott Klement, possiamo anche semplicemente ricavare il puntatore e la lunghezza della stringa JSON

yajl_beginObj();
.
.
yajl_endObj();

    rc = yajl_getBuf(jsonPtr:jsonLen);
    rc = http_post( URL
                  : jsonPtr
                  : jsonLen
                  : response
                  : HTTP_TIMEOUT
                  : HTTP_USERAGENT
                  : 'application/json');

    yajl_genClose();

Nel caso sopra otteniamo con yajl_getBuf(JsonPtr:JsonLen) il puntatore alla variabile e la sua lunghezza … che in fase di debug, se vogliamo vederne il contenuto potrebbe essere scomodo … anche se quando abbiamo un puntatore ad una variabile possiamo sempre vederne il contenuto con:

eval myPointer:c 10   // To display first 10 bytes addressed by myPointerc as a character string

or

eval myPointer:x 50  //  to display the first fifty bytes addressed by myPointer in hex

iJSON-FAQ-007: DATA-INTO con JSON Multi Oggetto e Array (e i consigli di Scott Klement)

Prendo spunto da una discussione sulla mailing-list di Midrange.com (Path and DATA-INTO and YAJLINTO) per vedere un esempio concreto di parsing di un JSON con diversi oggetti e array… con qualche consiglio di Scott Klement per ottimizzare le performance di DATA-INTO e YAJLINTO.

I sorgenti di questa FAQ, il documento JSON e altre info sono disponibili in questo repository GIST : Faq400Git/Data-Into-Multi-Objcets-Arrays

La struttura del JSON di esempio

Guardiamo il JSON e cerchiamo di capirne la struttura:

{
     ..
    "data": {
      ...
      "Invoice": [
            {
                "CreatedTimestamp": "2019-12-04T22:30:32.766",
                 ...
            },
            {
                "CreatedTimestamp": "2019-12-04T22:30:32.766",
                 ...
            }
                ],

Vediamo che c’è un oggetto esterno senza nome (il JSON parte subito con una parentesi graffa) … poi vediamo un oggetto “data” con dentro, a sua volta, un oggetto “Invoice” che contiene un array di fatture con i dati.

Le option di DATA-INTO e del Parser utilizzato

  • Per gestire l’oggetto senza nome più esterno, YAJLINTO permette di assegnargli un nome tramite le opzioni, nell’esempio vediamo “document_name”: “json” proprio per dargli il nome json all’oggetto stesso
  • Se a noi interessano i dati delle singole fatture (Invoice) possiamo indicare una PATH nelle opzioni di DATA-INTO per arrivare dentro quel dettaglio: ” path=json/data/Invoice “ … come si vede utilizziamo “json” come nome dell’oggetto più esterno
  • Un’altra cosa che vediamo nel JSON sono dei valori di tipo Timestamp … che il JSON non gestisce se non come campi stringa … quindi la cosa che conviene fare quando si hanno dei valori Timestamp è di metterli in campi char da 26 char nella nostra DS RPG

In pratica le opzioni sopra indicate andiamo ad impostarle come nel pezzo di sorgente che trovate qui sotto

// Options      
       RPGOPTS=' path=json/data/Invoice'
               +' doc=file'
               +' case=any'
               +' allowextra=yes'
               +' allowmissing=yes';

        YAJLOPTS='{"value_null":"0" '
              +', "document_name": "json" '
              +'}';
 // Parsing JSON
        data-into Invoice %DATA( ifsPathname : RPGOPTS)
                          %PARSER( 'YAJL/YAJLINTO' : YAJLOPTS);

Quanti elementi ha l’array dentro il JSON?

Quando abbiamo un array dentro il JSON “[ {…},{…}]”, se stiamo analizzando il livello stesso dell’array, YAJLINTO ritorna anche il numero di elementi trovato nella PSDS del programma e di conseguenza recuperarlo dopo il DATA-INTO, come nell’esempio che segue la variabile numElements:

 dcl-ds pgmStat psds;
           numElements int(20) pos(372);
 end-ds;

...
 data-into Invoice %DATA( ifsPathname : RPGOPTS)
                          %PARSER( 'YAJL/YAJLINTO' : YAJLOPTS);
 for i=1 to  numElements;
        string='InvoiceId(' +%editc(i:'Z')+ '): '
                 +invoice(i).InvoiceId;
        ...
 endfor;

I consigli di Scott Klement

Nel post sul forum Scott Klement consiglia di utilizzare le opzioni “allowextra=yes e allowmissing=yes” solo se la nostra DS non rispecchia il nostro JSON: se abbiamo invece la certezza del JSON meglio creare la DS che rispecchia ed evitare quelle due opzioni che rallentano l’esecuzione della DATA-INTO.

Un altra cosa che troviamo spesso nelle opzioni di DATA-INTO è “countprefix=xxx” e poi nelle opzioni del parser  “number_prefix”:”xxx” … queste due impostazioni servono quando si hanno degli elementi del JSON che iniziano come un numero, ad esempio { “30-day-outlook”: [ xxxx ] } … in questo caso il campo dovrà chiamarsi xxx_30-day-outlook nella nostra DS

Nei sorgenti del GIST trovate anche un esempio (XDATAINTO3_RPGLE) che esegue il parsing con DATA-INTO utilizzando una DS con tutti i livelli di oggetti presenti nel JSON (prestare attenzione che, in questo caso, il parser YAJLINTO noo tornerà più il numero di elementi trovati negli arrary perché l’elemento più esterno non è a sua volta un array!

iJSON-FAQ-008: YAJL and JSON dentro un JSON

Se ho un documento JSON già preparato … e lo voglio incapsulare dentro un altro JSON posso utilizzare la funzione YAJL_addPreformattedPtr.

Esempio:

YAJL_beginObj();
   YAJL_addBool('success' : stdOutput.success);
   YAJL_addChar('errCode' : stdOutput.errCode);
   YAJL_addChar('message' : stdOutput.message);
   YAJL_addPreformattedPtr('data'
                          : %addr(stdOutput.data:*DATA)
                          : %len(%trimr(stdOutput.data));
YAJL_endObj();

YAJL_copyBuf(CCSID
    : %addr(jsonData)
    : %size(jsonData)
    : wRetLen);
YAJL_genClose();

Related Posts
DB2 for i SQL – Stringhe – POSSTR-LOCATE-LOCATE_IN_STRING (IT)

Introduzione Spesso, nelle nostre applicazioni, abbiamo la necessità di lavorare con le stringhe di testo e l'SQL del DB2 può Read more

DB2 for i & SQL – FAQ & Howto (Part. 1) (IT)

Database DB2 e SQL ... forse lo strumento più potente e completo che abbiamo sulla piattaforma IBM i: ecco una Read more

Annuncio IBM i 7.4

Arriva direttamente con l'uovo di Pasqua questo annuncio IBM per le novità della versione IBM i 7.4, versione iNext secondo Read more

Generated Always Columns – Approfondimenti (IT)

Introduzione "Generated Always Column": sono colonne, campi, di una tabella il cui contenuto è controllato direttamente dal sistema ... e Read more

--- Roberto De Pedrini Faq400.com
About author

Founder di Faq400 Srl, IBM Champion, ideatore del sito Faq400.com e del Blog blog.faq400.com. Sviluppatore RPG da quando avevo i pantaloni corti, forte sostenitore della piattaforma IBM i (ex AS400), ho sempre cercato di convididere le mie conoscenze con gli altri tramite forum, eventi e corsi. Oggi, tramite Faq400 Srl, cerchiamo di aiutare le aziende a sfruttare al meglio questa fantastica piattaforma IBM i.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *