【第13回】RPGプログラマーのためのJSON入門(2):YAJLでJSONデータを使い尽くす | IBM i 総合情報サイト

【第13回】RPGプログラマーのためのJSON入門(2):YAJLでJSONデータを使い尽くす


Webサービスは、今や一般的なアプリケーションの1つになっていますが、IBM iではJavaなどのRPG以外の言語を使用して対応するのが一般的だったのではないでしょうか。しかし、RPGにも新たにDATA-INTOやDATA-GENという命令が追加され、RPGでWebサービスを構築できるようになっています。そこでまず、第12回、第13回ではデータ交換形式のJSONについての基本的な知識と扱い方を、第14回、第15回ではDATA-INTOとDATA-GENによるWebサービスのコーディングに関する基礎知識をご提供するために翻訳解説記事をお届けすることにします。


2017年9月 | ジョン・パリス、スーザン・ガントナー

前回の記事では、JSON文書を作成するためにYAJLライブラリ(特にスコット・クレメント氏のRPG向け実装)を使用することに焦点を合わせました。今回は、JSON文書を使い尽くし、データを抽出してRPGプログラムで使うためにYAJLを使用することに焦点を当てます。私たちが処理しようとしているJSON文書は、前回の記事で作成した顧客詳細情報文書を少し単純化したバージョンです。ファイルは次のようになります。

(1)  { "Customers": [
(2)       { 
             "ID": 12345,
             "Name": "Paris",
             "Street": "Main Street",
             "City": "Jasontown",
             "State": "CA",
             "Zip": "12345"
          },
(3)      { 
             "ID": 23456,
             "Name": "Rich",
           < ... 複数の顧客項目データは省略しています ... >
 
            "Zip": "98765"
         }
(4) ]
}

文書はCustomersという名前の配列で始まります。これに続いて(2)で最初の顧客のデータが開始され、”{” で始まります。個々の要素(ID、Nameなど)がそれに続き、それぞれコンマで区切られています。最初の顧客の最後の項目は、”}”によって終結します。

この文書では、前回の記事では触れなかったXMLとJSONのさらなる違いについて説明します。XMLでは、関連するフィールドをグループ化できるように、繰り返し要素(つまり顧客データ)に名前を付けるよう強制されます。JSONはすべてのものに対する名前を必ずしも必要とせず、ここでやったように単純に項目をオブジェクトにグループ化するだけで十分です。

これが文書の外観ですが、それをどのように処理するのでしょう?

YAJLを使ったJSONの処理

YAJLはJSONを処理する2つの方式を提供します。1つ目は、データに遭遇したときに個々のデータを処理するイベント駆動パーサーです。この点で、これはRPGのXML-SAX命令と多少似ています。

2つ目の方式(ここで使用するのはこれです)は、ツリーパーサーです。これは文書全体を一括処理し、基本的にデータ要素を名前で参照することができます。コードを見ると、それが分かります。

使用される基本的な方法は、最初にJSON文書全体を“ツリー”にロードしてから、処理したい文書の各レベルに対するノードロケーターを取得することです。ノードロケーターは、もしそう考えたいのであれば、識別子つまり“キー”と思って構いません。そして、次のレベルにドリルダウンするためにそのロケーターを使用します。RPGに実際にこれに相当するものはありませんが、その効果はファイルの順次READを行った後、そこからのデータを使用して他のデータをCHAINするのに似ています。

ご覧のとおり、YAJLにCustomers要素のロケーターを提供するよう要求し、それを使って個々の顧客配列要素に対するノードを見つけます。次にこれらを使って、顧客データを構成する個々のフィールドにアクセスします。コードを見れば分かるように、それは実際よりもはるかに複雑に思えます。

通常どおり、(A)でYAJLルーチンのプロトタイプをコピーします。次に、(B)でノードロケーターの定義を行います。コードを少し理解し易くするために、処理する要素毎に別々のノードロケーターを定義しました。それぞれはyajl_valと同じ属性の変数として定義されています。これらは実際にはポインターですが、スコット氏はポインター恐怖症の人を保護するために賢明にもこれを “隠し”ました。(C)では、抽出されたデータを保持するデータ構造配列を定義しています。

(A)   /copy yajl/qrpglesrc,yajl_h

      // Node locators
(B)    dcl-s  root           Like(yajl_val);
       dcl-s  addressNode    Like(yajl_val);
       dcl-s  customersNode  Like(yajl_val);
       dcl-s  customerNode   Like(yajl_val);
       ...

(C)    dcl-ds  customer  Dim(99)  Inz  Qualified;
         id       Zoned(5);
         name     Char(40);
         street   Char(40);
         city     Char(30);
         state    Char(2);
         zip      Char(5);
       end-ds;

処理を開始しましょう。この特定のJSON文書はIFSに格納されているので、(D)のAPI yajl_stmf_load_treeを使用して、1番目のパラメーターで指定された文書をYAJLの文書ツリーに読み込みます。ロードが成功すると、APIはルート要素に対するノードロケーターを返します。エラーが発生した場合、メッセージは2番目のパラメーター(errMsg)に格納されます。考えられるエラーには、ファイルが見つからない、またはアクセスできない、JSONデータの解析中に構文エラーが発生した、などが含まれます。

(E)では、エラーメッセージを検査して問題が発生したかどうかを確認し、エラーであれば適切な処置を行います。次に(F)でYAJL_OBJECT_FINDを使用してCustomersオブジェクトを探します。第1パラメーターとしてノードロケーターを指定したので、APIは検索を実行する場所、つまり検索するツリーの“ブランチ”を知っていることに注意してください。言うまでもなく、プロセスの開始時にはこれは常にyajl_stmf_load_treeによって返されるルートノードになる筈です。Customersオブジェクトが見つからなかった場合、何が起きるのか疑問に思うかもしれません。良い質問ですが、その答えは後ほどオプション要素の扱いについて説明するまでお預けにします。

(G)は、便利なYAJL関数YAJL_ARRAY_SIZEを示しています。これは、渡されたノードロケーターによって識別される配列内の要素の数を返します。この場合、Customers配列へのロケーターを渡しています。(H)のコードは、RPGの固定配列サイズに起因する問題を防ぐ方法を示すためだけに含まれています。このプログラムでは、配列内に99の顧客を許可しました。このコードにより、それ以上の数の顧客の処理が試みられないことが保証されます。

(D)    root
          = yajl_stmf_load_tree ( '/Partner400/Customers.json'
                                : errMsg );

       // エラー発生時には報告しプログラムを終了
(E)    if errMsg <> '';
         Dsply 'おっと - ファイルロード時に問題が発生しました!';
         // 適切なエラー処理を追加...
         Return;
       EndIf;

(F)    customersNode = YAJL_OBJECT_FIND( root: 'Customers' );

(G)    elements = YAJL_ARRAY_SIZE( customersNode );

(H)    If elements > %Elem(customer);  // 要素数が多すぎて処理不能?
          Dsply ('Can only process ' + %Char( %Elem(customer) ) +
                 ' customers - File contains ' + %Char(elements) );
          *InLr = *On;
          Return;
       endif;

これで、customer配列を指すロケーター(customersNode)が得られました。これを使用して、個々のcustomer要素のそれぞれを順番に処理することができます。これは、YAJL_ARRAY_LOOPを使用して行います(I)。これは、要素が見つかった場合は*On、処理する要素がない場合は*Offを返します。これにより、DOWループを使用して配列内のすべての要素を簡単に反復処理することができます。1番目のパラメーター(customersNode)は、その各要素を順次処理したいノードであり、2番目のパラメーター(C)は、開始する配列要素番号を識別するカウンターです(これについての詳細はこの後すぐに説明します)。3番目のパラメーター(customerNode)は、 各配列要素(つまりcustomer)に対するノードロケーターです。

カウンター(C)についての2つのポイント:

1つ目は、関数が呼び出されるたびに自動的に1増やされるということです。ですから、この後すぐに見るように、抽出されたデータをデータ構造配列にロードする際に配列の索引として使用できます。

2つ目は、取り出す要素を決定するために関数がカウンターを使用するということです。(H)では、最初の要素から開始したいので、ループに入る前にカウンターをクリアする必要がありました。

取り出されたcustomerNodeロケーターを使用して、直ちに構成要素のフィールドを抽出できます。まず(J)でYAJL_OBJECT_FINDを使用してIDフィールドを探し、次に(K)でYAJL_GET_NUMBERを使用してその値を抽出します。その結果は、カウンターを索引として使用してcustomerデータ構造配列の対応する要素に直接ロードされます。このプロセスは、各customerフィールドに対して繰り返されます。(L)では、まずNameフィールドの処理から始めています。文字フィールドに対してYAJL_GET_STRINGを使用し、対応するデータを抽出していることに注意してください。

本当にこれですべてです。

(H)    c = 0;
      
(I)    Dow YAJL_ARRAY_LOOP( customersNode: c: customerNode );

(J)       idNode = YAJL_OBJECT_FIND( customerNode: 'ID' );
(K)       customer(c).id = YAJL_GET_NUMBER( idNode );

(L)       nameNode = YAJL_OBJECT_FIND( customerNode: 'Name' );
          customer(c).name = YAJL_GET_STRING( nameNode );

          // ...すべてのフィールドに同様の操作を行う ...

       EndDo;

別のアプローチ

それらがすべて存在するならば、順番に各フィールドの値を要求することは問題ありません。しかし、もしも事故または設計(例えば、オプションのフィールド)で1つが欠落した場合はどうなるでしょうか? どうすれば見分けられるのでしょう?

私達が取れる基本的なアプローチが2つあります。1つ目は、単にYAJL_OBJECT_FINDによって返されたノードをテストして、nullであるかどうかを調べることです。根本的にすべてのノード値はポインターなので、実際のテストは次のようになることを忘れないでください。

      idNode = YAJL_OBJECT_FIND( customerNode: 'ID' );
         If idNode &tl;> *null;
            customer(c).id = YAJL_GET_NUMBER( idNode );
         Else;
            // 適切なエラー処理を行う

IDが必須フィールドならば、名前が与えられている可能性が高く、処理方法としておそらくこれは適切でしょう。しかし、もし文書にオプションのフィールドが含まれていたらどうでしょう? 各フィールドをすべて検査するコードを書くより簡単な方法はあるのでしょうか?

答えは“はい”です。YAJL_OBJECT_LOOP関数を使うことで、与えられたノード内のすべてのオブジェクトを単にループして、各フィールドの名前を取得することができます。 そして、SELECT操作でその名前を使用し、それぞれに見合った値を抽出できます。

以下の例では、(I)のYAJL_ARRAY_LOOP内でフィールドを抽出したコード(J、Kなど)を次のロジックに置き換えました。

(M)       i = 0;
(N)       Dow YAJL_OBJECT_LOOP( customerNode: i: key: node );
             Select;
(O)          When key = 'ID';
                customer(c).id = YAJL_GET_NUMBER( node );
             When key = 'Name';
                customer(c).name = YAJL_GET_STRING( node );
             When key = 'Street';
                customer(c).street = YAJL_GET_STRING( node );
             When key = 'City';
                customer(c).city = YAJL_GET_STRING( node );
             When key = 'State';
                customer(c).state = YAJL_GET_STRING( node );
             When key = 'Zip';
                customer(c).zip = YAJL_GET_STRING( node );
             EndSl;
          EndDo;

項目カウンター iをゼロに設定することから始めます。その後、項目カウンターは、(N)でYAJL_OBJECT_LOOP関数によって1増やされます。この関数には4つのパラメーターが必要です。1番目はノードロケーターで、ここからオブジェクトを抽出します。このケースでは customerNodeがそれです。2番目は前述の項目カウンターです。3番目は、関数がキー値(すなわちオブジェクトの名前)を配置する文字フィールドであり、関数は実際のロケーターノードを4番目のパラメーターで指定されたフィールドに配置します。YAJL_ARRAY_LOOPと同様に、この関数はDOWループを制御するための標識を返します。

次にSELECTをコーディングして、(O)で始まるWHEN句の個々のフィールド名を検査することができます。実際には、このアプローチを使用する場合、必須項目が実際に存在することを保証するためにロジックを追加する必要がほぼ確実にあります。また、オプションのフィールドに適切なデフォルト値が確実に設定されるようにする必要もあります。たとえば、データ構造配列内のオプションの文字フィールドを確実に空白にする必要があります。

まとめ

YAJLには、この2回の短いシリーズでカバーしたことよりもはるかに多くの利用可能な機能がありますが、このシリーズがあなたの欲求を喚起し、さらなる探究の出発点となることを願っています。

Copyright © IGUAZU