01 - Programmazione01a - RPG05b - XML e JSON

JSON e IBM i – FAQ & Howto (IT)

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 JSON

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

--- 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.

Rispondi

%d blogger hanno fatto clic su Mi Piace per questo: