【VisualForce】 フォームの空欄が null にならない問題


以前仕事で「レコードから取得した値がVisualforceページで変更されたか」をチェックするロジックを作成したところ、空欄のまま何も入力していない項目なのにどうもnull関連の条件分岐がイメージ通りに動かないことがありました。
「Visualforceの空欄を値として取得する際にnullの形式が変わってしまうのでは」というその時の推測を今回改めて検証してみました。以下のようなページを作りました。

画面

Visualforce

    <apex:form>
        <apex:inputText/>
        date   <apex:inputText value="{! testDate }"   />
        string <apex:inputText value="{! testString }" />
        id     <apex:inputText value="{! testId }"     />
        decimal<apex:inputText value="{! testDecimal }"/>
        <apex:commandButton value="テスト" action="{! doTest }" />
    </apex:form>

※カーソルフォーカスが値に影響する可能性を排除するため、左端には変数と結び付けられていないテキストボックスを配置しています。

Apex

public with sharing class NullTestCtrl {
    public String  testString   {set; get;}
    public Decimal testDecimal  {set; get;}
    public Id      testId       {set; get;}
    public Date    testDate     {set; get;}

    public NullTestCtrl() {
        Account acc = [
            SELECT
                Id,
                DateTest__c,   //日付
                StringTest__c, //テキスト
                IdTest__c,     //参照
                DecimalTest__c //数値
            FROM
                Account
            WHERE
                Name = 'GenePoint' //適当な取引先
        ];
        if (acc.DateTest__c    == null) { System.debug('### record date    : null'); }
        if (acc.IdTest__c      == null) { System.debug('### record id      : null'); }
        if (acc.StringTest__c  == null) { System.debug('### record string  : null'); }
        if (acc.DecimalTest__c == null) { System.debug('### record decimal : null'); }
        if (acc.DateTest__c    == null) { System.debug('### record date    : null'); }
        if (acc.IdTest__c      == ''  ) { System.debug('### record id      : \'\''); }
        if (acc.StringTest__c  == ''  ) { System.debug('### record string  : \'\''); }
        if (acc.DecimalTest__c == 0   ) { System.debug('### record decimal : 0'   ); }
        testDate    = acc.DateTest__c;
        testId      = acc.IdTest__c;
        testString  = acc.StringTest__c;
        testDecimal = acc.DecimalTest__c;
    }

    public PageReference doTest() {
        return null;
    }
}

検証:null はどう変わるのか

指定されているレコードの各項目は空です。
「テスト」ボタンを押下するとDecimalのみ空欄から0に変わります。

デバッグログにはこのように書かれています。

|VARIABLE_ASSIGNMENT|[EXTERNAL]|this.testDate|null|
|VARIABLE_ASSIGNMENT|[EXTERNAL]|this.testString|""|
|VARIABLE_ASSIGNMENT|[EXTERNAL]|this.testId|""|
|VARIABLE_ASSIGNMENT|[EXTERNAL]|this.testDecimal|0|

想定通り値の変化が起こっていることが分かりました。

  • Date型のnull → nullのまま
  • Decimal型のnull → 0に変化
  • Id型のnull → ''(空文字列)に変化
  • String型のnull → ''(空文字列)に変化

検証:null はいつ変わるのか

では、この変化は本当にフォーム上で起こるのか?デバッグログのコンストラクタ部分を見ていきます。

|DEBUG|### record date : null
|DEBUG|### record id : null
|DEBUG|### record string : null
|DEBUG|### record decimal : null
|DEBUG|### record date : null

Date以外は2度目のデバッグ出力を通っていません。SObjectの段階ではすべての項目の値がnullです。

|VARIABLE_ASSIGNMENT|[5]|this.testDate|null|
|VARIABLE_ASSIGNMENT|[4]|this.testId|null|
|VARIABLE_ASSIGNMENT|[2]|this.testString|null|
|VARIABLE_ASSIGNMENT|[3]|this.testDecimal|null|

各プリミティブ型の変数に値を代入した段階でもnullのままです。
さらに先ほどの画面のDecimal欄の変化から、Visualforce式で変数の値を表示させた段階でもまだ値はそのままであると分かります。よって以下のように考えられます。

  1. null はコード上の受け渡しでは null のまま
  2. null を入力欄の値として出力すると空欄となる
  3. 空欄を値として取得すると、変数型によっては null 以外になる

…ほぼ最初の推測どおりでした。
まだまだほかにも調べられるApexの変数型、Visualforceの入力要素の種類、Apex - Visualforce間の値の受け渡し方法はあるでしょうが、この記事はとりあえず以上です。