Webサービス、DATA-INTO、DATA-GEN パート3
今回の記事では、DATA-GENおよびDATA-INTOで使用されるいくつかのオプションについて取り上げます。JSONおよびXML内の要素名に、RPG名では非正規の文字が含まれていることがよくありますが、これらのオプションを使用することで、そうした状況に対処することができます。
このことが重要であるのは、RPGの-INTOおよび-GEN操作が、要素をマッピングする際に名前に依拠するためです。では、処理を行うドキュメントに、RPGでは非正規の名前が使用されている場合は、どのようにしたら対処できるのでしょうか。ご覧になれば分かると思いますが、-INTO操作の場合は、かなり簡単に処理されます。一方、DATA-GENでは、別の問題が生じます。これについては、後ほど説明します。
実際、-INTO操作で使用されるRPG名に対して適用される規則は、RPGの通常の規則に比べてさらに制限されています。すなわち、名前で使用可能とされる文字は、A-Zの文字、0-9の数字、およびアンダースコアのみです。規則がそれほど制限されている理由は定かではありませんが、ともかく、そういうことです。
この例で処理するJSONドキュメントの一部を見てみましょう。
"Customer Count": 3,
"Customer Data": [
{
"Customer Id": 12345,
"Customer Name": "Jacob Two-Two",
"Customer Address":
{ "Line #1": "123 Any Street",
"Line #2": "Streetsville",
"City": "Mississauga",
"State/Province": "ON",
"Zip/Post Code": "L5Q 2T8
}
} ...
変数名の規則に従うとすれば、ここには2つの問題があることになります。1つ目は、名前の中にスペースがある要素("Customer Count"など)が多数ある点です。2つ目は、スペースだけでなく「#」記号も含まれている要素名("Line #1"など)が2つある点です。
-INTO操作では、こうした状況を簡単に処理するための操作オプションが提供されています。それは「case=convert」というオプションです。このオプションを使用すると、要素名とRPG変数名とのマッチングを試みる前に、要素名を修正するようRPGに指示することができます。全体のプロセスを簡略化して示すと、以下のようになります。
- 大文字を小文字に変換する
- アクセント付き文字を、対応する大文字に変換する。つまり、à、è、îは、それぞれA、E、Iになる
- 残りの「非正規」文字をアンダースコアに変換し、連続するアンダースコアを1つのアンダースコアに変換する
- 名前の先頭がアンダースコアになった場合は、それを除去する
これらの規則を使用すると、このJSON内のCustomer要素に対応するDSの基本部分は、以下のようになります。
以下に、例で使用されるDS定義全体を示します。
Dcl-DS customer_T Template Inz Qualified;
<A> customer_Id char(5);
customer_Name varchar(40);
Dcl-DS customer_Address;
<B> line_1 varchar(50);
<C> num_line_2 int(5); // Line 2 is optional
line_2 varchar(50);
city varchar(50);
state_Province varchar(30);
<C> num_zip_Post_Code int(5); // And so is zip/post code
zip_Post_Code varchar(10);
End-DS;
End-DS;
// DS to receive response data extracted by DATA-INTO
Dcl-DS responseData Qualified Inz;
customer_Count int(5);
num_customer_Data int(5);
Dcl-DS customer_Data LikeDS(customer_T) Dim(99);
End-DS;
<A>では、"Customer Id"という名前の中にあったスペースを置換した結果、customer_Id という名前になったことが見て取れます。同様に、<B>では、"Line #1"という名前の中のスペースおよび「#」文字が1つのアンダースコアに置換され、Line_1という名前になっています。その他の要素名も、同じパターンに従っています。ここで1つ、指摘しておきたいことがあります。それは、これらの変数を定義する際に、意図的に、私がいつも使用しているキャメル ケース方式の命名規則(単語の先頭文字を小文字にする方式)を使用したことです。詳細は後述します。
もうひとつ、お気付きかもしれませんが、名前の先頭がnum_になっている要素を2つ導入しています <C>。これらを使用したのは、2つのJSON要素("Line #2"と"Zip/Post Code")が省略可能であることに対処するためです。これへの対処としては、allowmissing=yesというオプションを指定することによって、DATA-INTOに対して、要素の欠落が許容されるように指定することもできますが、そうすることによって問題が生じる場合もあります。このオプションを指定すると、省略可能であることが分かっている要素だけでなく、どの要素の欠落も許容されることになります。num_フィールドをcountprefix=num_オプションとともに使用することによって、その名前付き要素がいくつ存在するかカウントするようにRPGに指示することができます。要素が存在する場合、カウントは1、要素が省略された場合、カウントは0です。これで、他のいずれかの要素が欠落している場合、RPGはエラーを返しますが、これらの要素が欠落している場合、それらの状態を通知できるようにしてあるため、問題ありません。以下のコード例でこれらのオプションがどのように指定されているか確認してみてください。
Data-Into responseData
%Data( '/Home/Paris/JSONStuff/CustomerData.json'
: 'case=convert countprefix=num_ doc=file ' )
%Parser( 'YAJL/YAJLINTO' );
このオプションおよびその使用法の詳細については、2018年に記したXML-INTOに関する記事の中で、そのオプションについて取り上げていますので、 そちらの記事 を参照してください。DATA-INTOでも、まったく同じように機能します。
この例についてさらに深く掘り下げたい場合は、 こちらからコード パッケージをダウンロードすることができます。
非RPG名でJSON要素を生成する
このようなJSON要素を、利用するだけでなく、生成することが必要になったとしたらどうなるでしょうか。その場合、当然、選択肢となるのはDATA-GENですが、非RPG名の要件は、どのように処理したらよいでしょうか。
まず、先程、DATA-INTOでデータを格納したばかりのDS(responseData)を利用します。そのresponseDataをソースとしてDATA-GENを使用するだけです。どうなるか見てみましょう。以下は、その処理を行うDATA-GEN操作です。
Data-Gen responseData
%Data( '/Home/Paris/JSONStuff/CustomerDataOut.json'
: 'countprefix=num_ doc=file' )
%Gen( 'YAJL/YAJLDTAGEN' : '{ "beautify" : true }' );
これを見て、どうしてまた%Dataのcountprefix=num_オプションを指定したのか疑問に思われるかもしれません。省略可能な要素が出力されるかどうかはcountprefix=num_で制御される、と考えた方は、おめでとうございます。正解です。このオプションを指定していない場合、DS内のすべての要素が出力されますが、指定している場合は、0カウントと関連付けられている要素は、どれも出力されません。
これで、オリジナルと同じ基本的なデータが含まれるJSONファイルが生成されますが、 要素名に関する問題がなくなったわけではありません。DATA-GENは、JSON要素名としてRPGフィールド名を使用するからです。結果として、生成されるファイルは以下のようになります。
{ "customer_Count": 3,
"customer_Data": [
{
"customer_Id": "12345",
"customer_Name": "Jacob Two-Two",
"customer_Address": {
"line_1": "123 Any Street",
"city": "Mississauga", ...
ジェネレーターの{ "beautify" : true }オプションを指定していなかった場合、このJSONはこれほどきれいな見栄えにならなかったことに注目してください。本番目的では省略した方がよいですが、開発時には、そのオプションを指定することで、生成しているドキュメントが一段と見やすくなります。
すべてのアンダースコアが名前の中にまだ残っていますが、驚くことはありません。RPGは、不思議なことに、元の名前がどのようなものであったのか、解明することができません。これに対処する方法については、この後すぐに見て行きます。2つ目に注意すべきなのは、アンダースコアと関係がないところでも、たとえば、"city"要素など、問題が残っていることです。この名前はやはり誤りです。RPGとは異なり、JSONではin JSON名前の大文字小文字の違いは重要だからです。RPGでは、CITY、city、およびCityはすべて同じ変数ですが、JSONではそうではありません。JSONでは、それら3つはそれぞれ別の名前なのです。
したがって、DATA-GENによって使用されることになるDSをコーディングするときには、最初に思い起こすべきことは、自分流のコーディング標準をいったん忘れて、可能な限り、要素名の 正確な スペリングを使用しなければならないということです。RPGプログラムでは、どのように名前をタイプするかが重要になるのは、このケースぐらいでしょう 。DATA-GENは、タイプした通りのものをそのまま使用してしまいます。この例で言えば、たとえば、cityではなくCityという名前を使用するべきでした。
では、そのような非RPG名はどうなるのでしょうか。DATA-GENでは、これを処理するためのオプションが提供されています。そのオプションを使用すると、データを書き込む際に、RPG名の代わりに使用される名前を供給することができます。これはrenameprefixというオプションです。名前から推察されたかもしれませんが、countprefixオプションと同じように指定します。RPG変数の名前とは異なる名前が必要とされる出力の要素ごとに、実際に使用される名前を格納する追加の変数を用意するだけです 。このように言うと、実際よりずっと難しそうに思えてしまいそうなので、どのように使用するか、以下で説明しようと思います。
<
...
<D> name_customer_Data varchar(30) inz('Customer Data');
Dcl-DS customer_Data Dim(20);
<D> name_customer_Id varchar(30) inz('Customer Id');
customer_Id zoned(5);
name_customer_Name varchar(30) inz('Customer Name');
customer_Name varchar(40);
name_customer_Address varchar(30) inz('Customer Address');
Dcl-DS customer_Address;
name_Line_1 varchar(30) inz('Line #1');
line_1 varchar(50);
num_Line_2 int(5); // Line 2 is optional
name_Line_2 varchar(30) inz('Line #2');
Line_2 varchar(50);
City varchar(50);
...
Data-Gen outputData
%Data( '/Home/Paris/JSONStuff/CustomerDataNew.json'
<E> : 'doc=file countprefix=num_ renameprefix=name_' )
%Gen( 'YAJL/YAJLDTAGEN' : '{ "beautify" : true }' );
この例で必要とされる2つのname変数に注目してください <D>。それぞれのケースで、RPGが出力ストリームを構築する際に、JSON(または、他のドキュメントを生成する場合でも同様)での要素名としてnameフィールドの内容が使用され、関連付けられた値が、対応するペアから取得されます。そのため、name_customer_Idは、customer_Idに格納されるデータに名前を供給します(他も同様)。
<E> では、renameprefixオプションが使用されているのが見て取れます。実際の接頭部の値(すなわち、= 記号の後に続く文字列)は、任意の文字列にすることができますが、私にはname_が適切であるように思えました 。ご自分のショップで、煩雑さを解消し、混乱を避けるために、すべてのケースで使用される標準的な名前について、合意を取っておくことをお勧めします。
この記事に添付されているコード パッケージには、オリジナルのJSONを読み取り、2つの要素をアップデートし、DATA-GENを使用して新たなバージョンを書き出すプログラムが含まれています。コードをじっくり見てみると、DATA-GENが名前変更を処理するやり方のある欠点が明らかになると思います。JSON のアップデートを行う場合には、2つ目のDSを使用して、1つの構造から別の構造へデータをコピーする(ここではEVAL-CORRを使用)ことを強いられます。そのDSをname_変数とともに使用することもできますが、エラーを避けるためには、allowmissing=yesを使用する必要があります。それらのフィールドにデータが存在することはないからです。
次回
ここまでのところ、DATA-GENの使用例では、すべて1つの操作でJSONストリームを構築してきました。しかし、これは常に可能であるわけでも、また、実用的であるわけでもありません。状況によっては、JSONを部分ごとに構築する方がやりやすいケースもあるかもしれません。幸いなことに、DATA-GENにはそうするための機能が備わっています。このシリーズの次回の記事では、それがテーマとなります。