Webサービスは、今や一般的なアプリケーションの1つになっていますが、IBM iではJavaなどのRPG以外の言語を使用して対応するのが一般的だったのではないでしょうか。しかし、RPGにも新たにDATA-INTOやDATA-GENという命令が追加され、RPGでWebサービスを構築できるようになっています。そこでまず、第12回、第13回ではデータ交換形式のJSONについての基本的な知識と扱い方を、第14回、第15回ではDATA-INTOとDATA-GENによるWebサービスのコーディングに関する基礎知識をご提供するために翻訳解説記事をお届けすることにします。
2017年7月 ジョン・パリス、スーザン・ガントナー
IBM iを使用している現代の組織では、JSON(JavaScript Object Notation)が急速に重要な技術になりつつあります。Webサービスからブラウザーのインターフェースやデータ交換に至るまで、ここ数年でJSONの使用が著しく増加しています。最近、IBMはDB2にJSONサポートを追加しました。これは、別の機会に立ち戻るかもしれない話題です。
JSONは基本的にAjax呼び出しにおけるXMLの置き換えとして出発しました。Ajaxの“x”が実際に “Asynchronous JavaScript and XML” のXMLを表していると考えると、これはいささか皮肉なことです。 XMLはあまりにも大きく、動作が遅いことが判明しており、Web 2.0タイプのアプリケーションで解析するには、強大過ぎるほどの馬力が必要でした。一方、JSONはXMLよりはるかにコンパクトであるだけでなく、JavaScript自身のデータ定義をモデルにしているので、すべてのブラウザーに高速パーサーが事実上既に組み込まれています。ですから、ブラウザーは追加サポートをほとんど必要としませんでした。JSONもUTF-8によるコード化しか使用していないので、XMLでサポートされている複数種のデータコード化に対応する必要はありません。この3つの利点(コンパクトさ、解析の容易さ、UTF-8のすべて)によって、ほとんどのAjax呼び出しでJSONが急速にXMLを置き換えたのは驚くことではありませんでした。そこから、JSONはWebサービスの要求と応答のための選択肢となり、急速にデータ交換の領域に進出してきています。
JSON構文の基礎
それでは、JSONの書式設定の基本規則を見るところから簡単な紹介を始めましょう。JSONデータは一連の名前と値の対で構成され、コロンは名前と値の間の分離記号の役目をします。一連のデータ内の個々の要素はコンマで区切られ、名前は常に引用符で囲まなければなりません。ですから、JSONデータは下記のような基本的な形式をもちます:
"name1" : value_1, "name2" : value_2, ... "namen" : value_n
名前と値の対の実際の値の部分は、単なる数値または文字列以上のものにすることができます。実際、次のいずれでも構いません。
- 数値(整数または浮動小数)
- 文字列(引用符で囲まれている)
- 配列
- オブジェクト
- 理論値 true / false
- ヌル
配列は大括弧( “[“と “]”)で区切られています。複数の同じ型およびサイズの要素だけを含むRPGの配列と違い、JSONの配列には別の配列の他にあらゆる型の値を含めることができます。同様に配列は配列を含むことができ、さらにその配列は別の配列を含むことができ、さらに…と続きます。お分かりですよね。
オブジェクトは中括弧( “{“と “}”)で区切られ、オブジェクトをはじめ他の型の値を含めることができます。“オブジェクト”という用語に混乱しないでください。それらはオブジェクト指向(OO)とは何の関係もなく、基本的にRPGのデータ構造(DS)です。
本当にそれですべてです。ただし、文字列値に制御文字が含まれている場合(引用符がおそらく最も一般的です)、その前にバックスラッシュ文字(”\”)(訳注)を使用してエスケープする必要があります。ですから、文字列に引用符を含めるには次のようにコーディングします: “この値には\”引用符\”が含まれています”。同様に “\”文字自身は文字列の中で “\\”として記述されなければなりません。
少々単純化し過ぎかもしれませんが、基本を理解していただければ幸いです。規則が非常によく文書化されている(人によっては過剰文書化という意見もありますが!)XMLとは異なり、JSONの構文規則に関する情報は、様々な点でJavaScriptと必然的に切っても切れない関係にあるように見えるので、入手するのが困難な可能性があります。あなたがJavaScriptに完全に精通しているのなら良いのですが、私達はそうではありませんし、それは他の多くのRPGプログラマーにも当てはまるのではないでしょうか。
JSONの構文を基本的に理解したので、非常に簡単な例を見てみましょう:
(A) { (B) "employees" : [ (C) { "firstName":"John", "lastName":"Doe" }, { "firstName":"Anna", "lastName":"Smith" }, { "firstName":"Peter", "lastName":"Jones" } (D) ]}
(A) “{“で始まります。つまり、データはオブジェクトです。これは、文書ルートまたはルートノードとも呼ばれます。
(B)最初のフィールドの名前は “employees”で、値は配列 “[“です。
(C)配列の要素は同様にオブジェクトであり、それぞれが “firstName”と “lastName”に対する値を含んでいます。カンマは個々のフィールドを区切っています。
(D)最後に文書は配列を閉じ( “]”)、次にルートを閉じる( “}”)ことで終結します。
このデータをRPG のデータ構造で表現するとすれば、次のようになります。
dcl-ds employees Dim(3) Qualified; firstName Char(n); lastname Char(n); end-ds;
もちろん、データ構造配列内のフィールドの値を初期化するために必要なロジックは無視しています。さらに、RPG配列の要素数を指定しなければなりません。 皆さんの多くは既にXMLに精通しているかもしれないので、XML文書と等価なJSONのそれとを比較してその違いを見てみましょう。ここにXML文書があります。お分かりのように、お客様の詳細情報が繰り返されています。
<Customers> <Customer Id="G011739"> <Name>Langosh, Borer and Homenick</Name> <Address> <City>Danykafurt</City> <State>NV</State> </Address> </Customer> <Customer Id="E791196"> <Name>Denesik, Kessler and Rolfson</Name> ....
この前にRPGとの比較検討を行っているので、ここに等価なデータ構造を示します:
dcl-ds Customers; dcl-ds Customer Dim(3) Qualified; Id Char(7); Name Char(40); dcl-ds Address; City Char(30); State Char(2); end-ds; end-ds; end-ds;
ところで、JSONと等価なものはどのように見えるでしょうか? それをコーディングする1つの方法がここにあります。
{ "Customers": { "Customer": [ { "Id": "G011739", "Name": "Langosh, Borer and Homenick", "Address": { "City": "Danykafurt", "State": "NV" } }, { "Id": "E791196", "Name": "Denesik, Kessler and Rolfson", ...
もちろん、XMLと同様JSONは空白に依存しないので、以下に示すとおり、実際には文書はこれよりも以下に示すものにずっと近くなります。
{ "Customers": { "Customer": [ { "Id": "G011739", "Name": "Langosh, Borer and Homenick", "Address": { "City": "Danykafurt", "State": "NV" } }, { "Id": "E791196", "Name": "Denesik, Kessler and Rolfson", ...
このJSON文書作成に取り掛かる前に、XMLとJSONのバージョンの2つの違いに触れておく必要があります。第1に、JSONにはXML属性と同等のものがないので、”Id”属性は単に”Id”という名前のフィールドに変更されました。第2に、繰り返す”Customer”要素は配列(”Customer”: [)に置き換えられ、個々の顧客要素が単純にその配列内のオブジェクトになります。各オブジェクトには、 “Id”、 “Name”、および “Address”というフィールドが含まれます。 ここで、”Address”はオブジェクトであり、 同じく”City”および “State”フィールドが含まれています。
YAJLを使ったJSONの作成
“手で”(すなわち、単に文字列を一緒に繋げることによって)またはXMLを構築するために以前説明したようなテンプレート技法を使用することで、明らかにそのような文書を作成できます。しかし、JSONを作成すると同時に解析する必要もあるので、スコット・クレメント氏のYAJL移植版を両方のタスクに使用しようと決めました。YAJLの最新バージョンは、スコット氏のサイトから無料でダウンロードすることができ、そのインストールプロセスはとても簡単でよく文書化されています。また、YAJLは非常に高速であり、かつAPI呼び出しを使いやすくするためにスコット氏はそれをRPG化するという輝かしい仕事を成し遂げたことに言及しなければなりません。たとえば、彼のRPGインターフェースは自動的にEBCDICとUTF-8の間の変換を行います。
ところで、あなたが疑問に思っているといけないので言っておきますが、YAJLは“Yet Another JavaScript Library(もう1つのJavaScriptライブラリー)”の頭文字です。あまり有益な略語ではありませんけが!このパッケージは、この後すぐに分かるように、とてもメンテナンスしやすく理解しやすいJSON生成のためのAPI駆動アプローチを提供しています。
上に示したCustomers というJSON文書を生成するために使用したソースコードを見てみましょう。ここでは例としてコードのほんの一部を見ていきますが、完全なソースコードはここからダウンロードできます。
下記のコードでは、(E)で制御オプション(つまり、まだ完全な自由形式RPGを受け入れていない人にとってのH仕様書)から開始しています。最初に(ダウンロードで提供される)YAJLバインディングディレクトリを使うことを指定します。触れておく必要がある唯一の他のオプションはDecEdit(’0’)の仕様です。これはRPGに、%Charの数値変換に先行ゼロを常に含めるように指示します。これは、JSONでは .75などの10進数が無効だという理由で必要です。数値は数字で始まらなければならないので、これは0.75と表されなければなりません。この制御オプションを指定することにより、プログラムが正しく機能することが保証されます。
(F)では、YAJLルーチンのすべてのプロトタイプと定数をコピーしています。続いて(G)で必要なエラーメッセージ・フィールドの定義を行っています。後で、これが使用されている様子を見ます。
(E) ctl-opt DftActgrp(*No) BndDir('YAJL') Option(*SrcStmt) DecEdit('0.'); (F) /copy yajl/qrpglesrc,yajl_h dcl-s i Int(5); dcl-s wait Char(1); (G) dcl-s errMsg VarChar(500) Inz;
このコードでは、yajl_呼び出しを文書の “形状”に合わせるために桁ずらししていることに注意してください。
(H)でyajl_genOpen()プロシジャーを呼出し、メイン処理が開始されます。これでJSON生成プロセスが開始されます。値*Onを渡すことで、YAJLに “きれいに”形式化された出力を生成するよう指示します。生成における問題を診断しやすくするために、私たちは通常これを開発フェーズで行います。プログラムが動作して本番稼働に入る準備ができたら、このパラメーターを単に*Offに変更し、JYAJLにコンパクトな非形式化文字列を生成させます。
次に、(I)でyajl_beginObj()を呼び出します。パラメーターを渡さないと、文書全体を含む新しいJSONオブジェクトを開始するようAPIに信号が送られます。これはルートオブジェクトと呼ばれます。すぐにこれに続き(J)で”Customers”という名前のオブジェクトを開始するために、同じAPIへの別の呼び出しを行います。 Customersオブジェクトは顧客の詳細情報を持つ配列で構成されているので、次の(K)ではyajl_beginArray()を呼び出して配列を開始します。
// メインプロセスの開始 ... (H) yajl_genOpen(*On); (I) yajl_beginObj(); // ルートオブジェクトの開始 (J) yajl_beginObj('Customers'); (K) yajl_beginArray('Customer');
この時点で、JSON文書の土台は準備万端整っています。これで、私達はただ個々の顧客のデータを追加するだけです。これは単なるサンプルプログラムなので、データファイルなどの作成を心配せずに、自分のシステムにコピーして “実行”できるようにしたいと思います。ですから、単純にForループを使って 3つの顧客データを生成し、いずれの場合も現在のForループインデックスの単なる文字(%Char)バージョンを実際のデータ値として使用します。明らかに実際の作業バージョンでは、データはSQLクエリまたはRPGのI/O操作によって供給されることになるでしょう。
配列Customerの各要素はオブジェクトなので、(L)でオブジェクトを開始し、次に(M)でIdおよびNameフィールドの値を追加します。 AddressはCityとStateを含むオブジェクトなので、(N)でAddressを開始し、(O)で必要なフィールドを追加します。次に、(P)でyajl_endObj()を2回の連続して呼び出し、Addressと配列要素オブジェクトを終結してから、次の顧客を処理するためにループの先頭に戻ります。
for i = 1 to 3; (L) yajl_beginObj(); (M) yajl_addChar('Id': %Char(i)); yajl_addChar('Name': %Char(i)); (N) yajl_beginObj('Address'); (O) yajl_addChar('City': %Char(i)); yajl_addChar('State': %Char(i)); (P) yajl_endObj(); yajl_endObj(); EndFor;
すべての顧客情報が出力されると(すなわち、Forループが終了すると)、(Q)で配列Customerを終結し、Customersオブジェクトおよびルートオブジェクトを終結します。JSON文書が完成し、それを使って私たちが望むことを何でもする準備ができました。この簡単な例では、プログラムの実行結果を見やすくするために、(R)でバッファー全体をIFSファイルに保存しています。ファイル名に加え、エラーメッセージフィールド(errMsg)も提供していることに注意してください。IFSへの書き込み中にエラーが発生した場合、このフィールドには適切なエラーメッセージが含まれます。そうでない場合、それは空の文字列になります。この可能性は(S)で検査されます。
(Q) yajl_endArray(); yajl_endObj(); // 顧客オブジェクトの終結 yajl_endObj(); // ルートオブジェクトの終結 (R) yajl_saveBuf('/Partner400/JSONEX2.json': errMsg ); (S) if errMsg <> ''; Dsply 'おっと、問題発生です!' ' ' Wait; EndIf; *InLr = *On;
それですべてです。非常に単純なプロセスであり、複雑な文書を生成する場合でも比較的単純なプロセスのままです。
おわりに
JSONを生成する時間の大部分は、ブラウザーのAjax要求への応答をフォーマットしたり、Webサービスの要求文書または応答文書を作成したりするために費やされます。私達がここで行ったこととそれとの唯一の実際の違いは、yajl_saveBuf()の呼び出しをyajl_copyBuf()の呼び出しで置き換えることです。yajl_copyBuf()はJSON文字列を受け取り、適切な出力ルーチンに渡す準備のできている通常のRPGフィールドにコピーします。
JSON文書を開発する際には、ここで示したIFS機能を活用するのが大変便利です。その理由は、これによって結果をJSON検証ソフトで調べられるようになるからです。 そうすることで、文書の送信を開始し、JSONの生成またはそれに続く処理が問題の原因であるかどうかについて他の開発者と議論する前に、文書が有効であることを確認できます。世の中には、JSONLintやFreeFormatter.comを含む多くの検証ソフトがあります。後者には、検証のためにファイルをアップロードできるという追加の利点があります。
次回の記事では、YAJLを使ってどのようにJSONを処理するかを調べます。
(訳注)
バックスラッシュ文字は本来”\”(0x5C)ですが、ご使用のブラウザー環境によってはこれが表示上円記号”¥”になることがあるかもしれません。その場合は適宜”¥”と読み替えてください。