yonet77的な雑記帳

日々思いついたネタなどを書き留めておきます

Force.comでのJSON Support -ApexコントローラのRemoteActionを添えて-

Force.comでのJSON Support - yonet77 的な雑記帳 の続き的な感じで。
せっかくForce.comでJSONが扱えるようになったので、外部システムと連携させてみたい...と思うのが人の性でしょうか。
そんな直球勝負(?)は避けて、今回は小賢しい話を紹介してみたいと思います。

ApexコントローラのRemoteAction

Spring '11 からJavascriptを介してApexコントローラのメソッドを呼び出せるようになりました。
元々、apex:actionFunctionコンポーネントを使うと、Apexコントローラのメソッドを呼び出すことはできましたが、Visualforce Page内のどこかのDOMを再描画させるもので、コールバックは用意されてませんでした。
apex:inputHiddenのvalueを介して、apex:actionFunction/apex:actionStatusコンポーネントを組み合わせながら値をやり取りすることが多かったかもしれません。
(まぁ、自分もそうしてました。結構面倒だった記憶が....)

そこで、このRemoteActionを使ってみる、というワケです。
※なお、RemoteActionについての説明は コチラ を参照下さい。

  • RemoteActionを使って、SObjectのデータを取得する

※実際には、JSONを使ってませんがついでに。。
例えば、こんな感じにRemoteAction使うとデータを簡単に取得できます。
■Apexコントローラ

global with sharing class SampleRemoteAction {
  @RemoteAction
  global static List<sObject> queryData(String query){
    return Database.query(query);
  }
}

■Visualforce Page

var soqlQuery = 'Select Name,AccountNumber From Account';
SampleRemoteAction.queryData(soqlQuery, function(res, event){
  if(event.status){
    // resに取得したデータが格納されているので、データを好きに処理..
    var rowData = res;
  } else {
    // Error
  }
},{escape:true});
  • RemoteActionを使って、JSONデータを受け取ってApex側で処理する

Apexコントローラ側では、受け取ったJSON文字列をdeserializeして、Apexオブジェクトに変換します。
変換したApexオブジェクトに対して、適当にデータ処理すれば良いでしょう。
■Apexコントローラ

global with sharing class SampleRemoteAction {
  @RemoteAction
  global static String processData(String paramJSON){
    List<Sample> objs = (List<Sample>)JSON.deserialize(paramJSON, List<Sample>.class);
    for(Sample wk : objs){
      // 適当なデータ処理(DML発行)など
    }
  }
}
public class Sample{
  public String key;
  public String name;
  public Integer index;
}

■Visualforce Page
※ここでは、jQueryを使って、JSON文字列に変換してます。
Visualforce Page側は、例えば処理したいデータを格納したオブジェクトをJSON文字列に変換して、上記コントローラのRemoteActionに渡せば問題ないと思います。

var updates = [];
updates.push({'key':'aaa', 'name':'bbb', 'index':100});
SampleRemoteAction.processData(jQuery.toJSON(updates), function(res, event){
  if(event.status){
    // Success
    alert('Success!');
  } else {
    // Error
    alert('Error: ' + event.message);
  }
},{escape:true});

Visualforce PageとApexコントローラの間でJSON形式でデータをやり取りする基本形は上記の感じかと思います。

でもちょっと面倒臭くない?

Visualforce PageからApexコントローラにクエリを投げてデータを取得する方に関しては、Visualforce Page側で正しくクエリさえ作れれば、標準オブジェクト/カスタムオブジェクトでも特に問題なくデータが取得できるかと思います。
一方、後者の方では、事前にdeserializeする型(上記例では、Sampleクラス)を用意しておかなければならないという面倒臭さが匂います。
単に型だけを定義した何てことのないクラスがたくさん生成される(コピペの予感)...なんて状況は気に入らないですよね。
スピードワゴンに言わせれば、

こいつはくせえッー! コピペコード大量生成のにおいがプンプンするぜッ―――ッ!!
こんな冗長なコードには出会ったことがねえほどなァ――――ッ
環境で冗長になっただと? ちがうねッ!!

と、ロウソク台を蹴りつけられる感じかもしれません。 (/++)/
特に、カスタムオブジェクトをCreate/Updateだけしたい場合なんて、オブジェクト定義用のクラスを都度用意するのも面倒です。

ここでは、単純にカスタムオブジェクトをCreate/Updateしたいだけなら、もう少し簡単に済ませたいものです。
ということで、こんな感じでいけるんじゃないか?という手を考えてみました。

■Apexコントローラ

global with sharing class SampleRemoteAction {
  @RemoteAction
  global static String processData(String objName, String paramJSON){
    try{
      Type objType = Type.forName('List<' + ObjName + '>');

      List<SObject> sObjs = (List<SObject>)JSON.deserialize(paramJSON, objType);
      upsert sObjs;

      return '';
    }catch(Exception ex){
      return ex.getMessage();
    }
  }
}

■Visualforce Page

var updates = [];
updates.push({'Name':'aaa', 'AccountNumber':'bbb', 'AnnualRevenue':100});
SampleRemoteAction.processData('Account', jQuery.toJSON(updates), function(res, event){
  // 第一引数にカスタムオブジェクト名、第二引数にJSON文字列を指定します
  if(event.status){
    // Success
    alert('Success!');
  } else {
    // Error
    alert('Error: ' + event.message);
  }
},{escape:true});

Apexコントローラに、カスタムオブジェクト名を引数として渡して、動的にdeserializeする型を指定する、という感じです。
このやり方であれば、Visualforce Page上で扱うカスタムオブジェクトが何であれ、カスタムオブジェクト名とそのデータ項目が分かっていれば、RemoteAction経由でApexコントローラに渡して処理できる...
というのを狙ってます。

もちろん、REST APIを使えばもっと楽になりますが、APIコール数の消費を回避できるので、APIコール数を気にせずに処理できる、という感じです。
Apex:actionFunction使っていた頃よりもパフォーマンス的には優れているので、最近はRemoteActionお気に入りですw

でもちょっと惜しい!

上記の方法を使えば、特に問題なく処理できるように思えますが、1点解決しなければいけない問題が残ってます。
Force.comでのJSON Support - yonet77 的な雑記帳 でも書きましたが、管理パッケージ下にあるオブジェクトをdeserializeする型に指定した場合、日付のパースがうまくできない、という問題です。。
これについては、今後回避策など検討予定です...

そんなわけで、現時点ではカスタムオブジェクトの項目を定義した型を用意するのが安全のようです。。。残念。
(管理パッケージ下のオブジェクトでなければ、上記のやり方でも大丈夫だとは思いますが。)

ちなみに、
Community - Don't know the type of the Apex object to deserial... - Page 4 - Force.com Discussion Boards
でも似たような問題に遭遇した事例が取り上げられてるみたいです。今後改善されるのかなー?と期待しつつ、今回はここまで!
ちなみに、次回も未定です。

Enjoy, JSON Support & Apex RemoteAction!

なお、このエントリーはForce.com Advent Calendarに参加しています
僕の担当がかなり遅くなってしまって、後続の担当者に多大な迷惑をかけてしまったこと、お詫びします。。。m(_ _)m