while関数の深掘り。使い所などをJSON関数と絡めて


FileMakerも他のwebサービスやアプリとの連携が一気に加速しています。Claris社もそっちに一気に舵を切っている感じがしますので、連携に必須のJSON作成ネタに絡めてwhile関数の話を。

ver16から登場のJSON関数ですが、正直記述が面倒です。なのでver18で登場したwhile関数と組み合わせて、レイアウト上にあるフィールドデータを
{フィールド名 : 値}でまるっとJSON化させ、これを題材にwhile関数の動きを追ってみたいと思います。
スクリプトでも手間は大して変わりませんが、汎用的にどこでも使えるし、while関数の理解と使い道としていいかと思いネタ化してみました。
また、GoogleMap APIから住所データを取得する際もwhile関数を使うのが適しているので後半に題材にします。
while関数は昔からあるLet関数と性質が同じなので、適宜Let関数に置き換えてwhile関数の動きを追ってみたいと思います。

対象となる方

  • while関数をまだあまり使っていない方、ガッツり学習したい方。
  • スクリプトではなく計算式でサクッとJSONデータを作成したい方。
  • googleMap APIを利用して郵便番号から住所検索したい方。

ここで出来るようになる事

  • レイアウト上のフィールドデータを一発でJSON化できる(汎用的にどのファイルでも使える。)
  • フィールド定義で繰り返し処理が使えるようになる。
  • while関数の使い方がわかる。
  • googleMap APIを利用して郵便番号から住所を取得できる。

最初にフィールドデータをまるっと JSONデータ化

用意するもの

JSON化したいフィールドのみを表示させたレイアウト
(ここでは関連レコードは基本的に含めません。単一テーブルから引っ張ってきたJSON化に必要なフィールドのみを配置します。)
この後にFileMaker付属のテンプレート[請求書]を題材にサンプルを掲載しています。

先に計算式の内容です。

FileMaker
While ( 
 [ 
 #fileName = Get ( ファイル名 );
 #layoutName = Get ( レイアウト名 ) ;
 #fields=FieldNames (#fileName ; #layoutName) ; 
 #fieldsCount = ValueCount ( #fields ); 
 #i=0;
 #result = ""
 ] ; 
 #i < #fieldsCount ; 
 [  
 #i=#i+1;
 #name = GetValue ( #fields;#i ); 
 #value = GetField ( #name ); 
 #fieldType = FieldType ( #fileName;#name ); 
 #jsonType = Case ( PatternCount ( #fieldType ; "Number");JSONNumber;JSONString); 
 #result = JSONSetElement ( #result;#name;#value;#jsonType ) 
 ];
 JSONFormatElements ( #result)
  )

while関数とJSON関数の組み合わせで、計算式でフィールド上の全データをJSON化する事が出来ます。なにより記述が面倒なJSONSetElementを長々と書く必要がありません。
while関数が出るまではEvaluate関数での再帰処理を使っていましたが、少々記述が難しいところがありました。while関数の登場でようやく分かりやすい形で記述できるようになりました。
これを題材に順を追って説明します。

まず、while関数の記述形式は

while関数
While ( [ 初期変数 ] ; 条件 ; [ ロジック ] ; 結果 )

です。Let関数と同じ感じです。

  • 初期変数:[ ]の中に複数記述できる初期変数(ロジックを調理とすると、これは具材に相当します)
  • 条件:ロジックの動作条件(この条件式に当てはまる間は下のロジックの処理を繰り返します)
  • ロジック:[ ]の中に複数記述できる処理。この処理を動作条件に合致している間繰り返します。
  • 結果:出力する結果です。

上記計算式をこの記述形式に沿って分解して見ていきます。

初期変数部分

初期変数
[ 
 #fileName = Get ( ファイル名 );  //3行目のFieldNames関数で使用します。
 #layoutName = Get ( レイアウト名 ) ;  //3行目のFieldNames関数で使用します。
 #fields=FieldNames (#fileName ; #layoutName) ;  //レイアウト上に配置しているフィールド名を全部リスト化
 #fieldsCount = ValueCount ( #fields );  //3行目でリスト化したフィールドの数をカウント
 #i=0;  //処理回数をカウントする為のカウンター
 #result = ""  //最終処理結果を入れる為の入れ物。
 ] ; 

(#fileNameと#layoutNameは変数にするまでもないですが、敢えて細切れにしています。)
レイアウト上に表示されたフィールド名をリスト化した状態になっています。この部分だけをそのままLet関数に置き換えると中身がよく分かります。

Let関数で表記
Let ( 
 [ 
 #fileName = Get ( ファイル名 );
 #layoutName = Get ( レイアウト名 ) ;
 #fields=FieldNames (#fileName ; #layoutName) ; 
 #fieldsCount = ValueCount ( #fields ); 
 #i=0;
 #result = ""
 ] ; 
#fields & ¶ & #fieldsCount
  )

この状態でフィールド名の一覧とフィールドの個数が取得されているはずです。これを初期変数として使用します。

条件部分

条件
#i < #fieldsCount ; 

カウンターがフィールドの個数に到達するまで処理を繰り返します。

ロジック部分

ロジック
 [  
 #i=#i+1;  //カウントアップ(重要)
 #name = GetValue ( #fields;#i );  //フィールドリストの#i番目のフィールド名を取得
 #value = GetField ( #name ); //フィールドの値を取得
 #fieldType = FieldType ( #fileName;#name );  //フィールドの型を取得
 #jsonType = Case ( PatternCount ( #fieldType ; "Number");JSONNumber;JSONString);  //数字以外はテキスト型へ

 #result = JSONSetElement ( #result;#name;#value;#jsonType )  //json組み立て
 ];
JSONFormatElements ( #result)

右のコメントアウトでわかると思いますが、初期変数で得たフィールドリストをGetValue関数で一つづつ取り出してそのフィールド値を取得していきます。
まず最初に大事なカウントアップ#i+1 これを設定しておかないと無限ループしてしまいます。重要。
#nameにフィールド名
#valueにそのフィールドの値
JSON化するにあたってはデータの型情報が必要になってきますので、FieldType関数で取得します。Number,Text,Dateとか取得できますが、JSONでは日付はText型にしますので、ここではNumber型のみ判別させてJSONNumberとし、それ以外は全てJSONStringとして#jsonTypeにフィールドの型を入れます。

そして上記変数で設定した処理結果をJSONSetElementでJSON化して#resultに入れる。

以上の処理を[#i < #fieldsCount] の間繰り返し、#resultに結果が追加されていきます。
結果の出力は#resultのみでもいいですが、読み易いようにJSONFormatElements ( #result)で整形して出力します。

ここまでの処理を再度Let関数で置き換えて表示させてみます。

(*条件部分の#i < #fieldsCountはカットしています)

Let関数
Let( 
 [ 
 #fileName = Get ( ファイル名 );
 #layoutName = Get ( レイアウト名 ) ;
 #fields=FieldNames (#fileName ; #layoutName) ;
 #fieldsCount = ValueCount ( #fields ); 
 #i=0;
 #result = "";
//ここまでが初期変数部分

 #i=#i+1;
 #name = GetValue ( #fields;#i ); //フィールド名を一つづづ取得
 #value = GetField ( #name ); //フィールドの値
 #fieldType = FieldType ( #fileName;#name ); //フィールドの型を取得
 #jsonType = Case ( PatternCount ( #fieldType ; "Number");JSONNumber;JSONString); //数字以外はテキスト型へ
 #result = JSONSetElement ( #result;#name;#value;#jsonType ) 
//ここまでがロジックの部分
];
 JSONFormatElements ( #result)
  )

この状態ではFieldNamesで改行区切りでリスト化したフィールドの1行目のデータのみ以下のようにJSONフォーマットで表示されます。

Let関数の結果
{
"フィールド名" : "そのフィールドの値"
}

こうやって分解してLet関数に置き換えてみると、while関数はLet関数の途中に繰り返し条件を挿入しただけです。

この計算式をスクリプトに置き換えてみます。

while関数での計算式とほとんど手順も内容も変わりません。
終了条件が逆になっていて、Loopの内側に記述しているだけです。このスクリプトならレイアウト依存しないのでどこでも使い回しができます。

スクリプトの利点はデバッグが細かく出来るという事ですね。なので複雑なロジックの組み立てはまずスクリプトでやってみて、その後に必要であればwhile関数に焼き直すのがいいかと思います。
計算式の利点はフィールド定義でそのまま値を保持できるという事。while関数の最大の利点はフィールド定義で繰り返し処理を利用しやすくなった事ですね。

例としてFileMaker付属のテンプレート[請求書]から

付属テンプレートの中の請求書で新規ファイルを作成。
製品レイアウトを選択して新規レコードを作成し、適当にフィールドを埋めてみます。

そしてデータビューワーの監視タブに式を追加して、最初に書いたwhile式を放り込んでみると、以下のようなJSONが取れているはずです。
関連テーブル(注文::で始まる部分)もポータルで配置されているのでその部分も取れています。これはJSONのキーネームとしては使いにくいので、実際には関連レコードを排除した、単一テーブルのフィールドで構成したJSONデータ用レイアウトを用意した方が使いやすいです。(関連レコード部分に対しても計算式内で一発処理できますが、ちょっと複雑になるのでここでは割愛)

JSON
{
    "SKU" : "",
    "カテゴリ" : "サービス",
    "ファイルオブジェクト" : "",
    "価格" : 300,
    "再注文レベル" : 0,
    "在庫" : "サービス",
    "在庫数" : 0,
    "注文::コメント" : "",
    "注文::プロバイダー" : "",
    "注文::数量" : 0,
    "注文::日付" : "",
    "注文::注文 #" : "",
    "注文::金額" : 0,
    "製造元" : "Claris",
    "詳細" : "FileMakerCloud",
    "課税対象" : 1,
    "購入価格" : 100,
    "項目" : "新製品"
}

もういっちょ、GoogleMapAPIから取得した住所データをwhile関数で取得する。

今度は受け取ったJSONデータをwhile関数で切り出すというケースです。

GoogleMapAPIで郵便番号(150-0043)を入れると、

cli
https://maps.googleapis.com/maps/api/geocode/json?language=ja&address=150-0043&key=__YOUR_API_KEY__

( __YOUR_API_KEY__の部分は置き換えて下さい。)

このようなJSONデータが返ってきます。
{
"results" : [
{
"address_components" : [
{
"long_name" : "150-0043",
"short_name" : "150-0043",
"types" : [ "postal_code" ]
},
{
"long_name" : "道玄坂",
"short_name" : "道玄坂",
"types" : [ "political", "sublocality", "sublocality_level_2" ]
},
{
"long_name" : "渋谷区",
"short_name" : "渋谷区",
"types" : [ "locality", "political" ]
},
{
"long_name" : "東京都",
"short_name" : "東京都",
"types" : [ "administrative_area_level_1", "political" ]
},
{
"long_name" : "日本",
"short_name" : "JP",
"types" : [ "country", "political" ]
}
],
"formatted_address" : "日本 〒150-0043",
"geometry" : {
"bounds" : {
"northeast" : {
"lat" : 35.6615132,
"lng" : 139.7018055
},
"southwest" : {
"lat" : 35.6555252,
"lng" : 139.6948839
}
},
"location" : {
"lat" : 35.6581518,
"lng" : 139.6981574
},
"location_type" : "APPROXIMATE",
"viewport" : {
"northeast" : {
"lat" : 35.6615132,
"lng" : 139.7018055
},
"southwest" : {
"lat" : 35.6555252,
"lng" : 139.6948839
}
}
},
"place_id" : "ChIJb9jWkqmMGGARhzcIvhmNywM",
"types" : [ "postal_code" ]
}
],
"status" : "OK"
}

ここで住所を取得するのに必要なのは、”results[0].address_components”以下の部分なわけですが、このaddress_componentsは住所によって値が4個だったり6個だったりと変動要素になります。
今までここはLoop処理で回して取得していましたが、while関数だと一行で書けるのでスクリプトが簡潔になります。

$jsonDataに上記GoogleMapAPIからの結果が入っていると仮定して、計算式は

code.cli
While ( 
[
#data = JSONListValues ( $jsonData; "results[0].address_components" );
#count = ValueCount ( #data ) - 2;
#result = ""
];

#count > 0 ;

[
#str = JSONGetElement ( $jsonData; "results[0].address_components[" & #count & "].long_name" ) ;
#result = #result & #str;
#count = #count - 1
];

#result
)

結果は東京都渋谷区道玄坂

翻訳すると、

[
#data = JSONListValuesでaddress_components以下のデータをリスト化
#count = そのリストの個数を数える
#result = 計算結果の入れ物
];
繰り返し条件は #countが1になるまで。

[
#str = address_componentsの中の#count番目のデータを取得
#result = 結果を入れる#resultに#strを放り込む。
#count = #count -1  カウントダウンする
]

#result 最終の結果を返す

という流れになります。

これで例えば住所を入れるフィールドにフィールド設定でこの計算式を設定すれば僅か1行のスクリプトで完結します。Loopスクリプトで書くのと手間は同じですが、スクリプトの見通しが全然良くなるので、ちょっとした繰り返し処理が必要なフィールド設定なんかにはwhile関数は適していますね。

実はYoutubeにこのGoogleMapAPIから住所を取得するまでの実況動画をUPしてみたのです。
https://youtu.be/uGCauVw0SD0
動機はDavinci resolveを使い始めたので何か作ってみよう、、、という理由ですwww.
グダグダなのでちょっと聴くに耐えないかもしれませんが、もし覗いてみる方は倍速再生して下さい。

まとめ

ver18以降でないと使えない関数なのでいまいち情報が少ないなと思い書いてみました。
実際自分の職場でもまだcloud側がver16なので実戦投入出来ていません。

Let関数の発展系なので、スクリプトに組み入れずとも、データビューワーでちょこちょこ確認する時などにも使えますし。

繰り返し処理は複数の処理方法が選択でき、場面場面で適宜使い分け出来るようになっておいた方がいいですね。
JSONデータの処理などではwhile関数の方が楽な気がします。