ダイナミック・エンティティの作成と変更
この章では、ダイナミック・エンティティが機能する仕組みについての基本的な情報を提供します。以下の項目について説明します。
JSON リテラル・コンストラクタの使用
ダイナミック・エンティティは、%DynamicObjectOpens in a new tab または %DynamicArrayOpens in a new tab のインスタンスで、JSON のデータ操作を ObjectScript アプリケーションにシームレスに統合するように設計されています。標準の %New() メソッドを使用してこれらのクラスのインスタンスを作成できますが、ダイナミック・エンティティは、これよりはるかに柔軟で直感的な一連のコンストラクタをサポートしています。JSON リテラル・コンストラクタを使用すると、JSON 文字列を変数に直接代入することでダイナミック・エンティティを作成できます。例えば、以下のコードは %DynamicObjectOpens in a new tab と %DynamicArrayOpens in a new tab の空のインスタンスを作成します。
set dynamicObject = {}
set dynamicArray = []
write dynamicObject,!,dynamicArray
3@%Library.DynamicObject
1@%Library.DynamicArray
%New() コンストラクタとは異なり、リテラル・コンストラクタの {} と [] は JSON フォーマットの文字列を引数として受け取ることができます。例えば、以下のコードは prop1 という名前のプロパティを持つダイナミック・オブジェクトを作成します。
set dynamicObject = {"prop1":"a string value"}
write dynamicObject.prop1
a string value
実際には、JSON リテラル・コンストラクタの {} と [] を使用して、任意の有効な JSON 配列構造またはオブジェクト構造を指定できます。簡単に言うと、有効な JSON リテラル文字列はすべて、評価結果がダイナミック・エンティティとなる有効な ObjectScript 式でもあります。
JSON 言語仕様 (https://json.org/Opens in a new tab を参照) は JavaScript Object Notation のサブセットであり、この言語仕様では一部の領域でより厳格な規則が適用されます。重要な相違点は、JSON 仕様ではすべてのプロパティ名を二重引用符で囲む必要があることです。これに対して、JavaScript 構文では多くのケースで引用符なしの名前が許可されます。
ダイナミック・エンティティには、JSON 文字列内のそれぞれのオブジェクト・プロパティまたは配列要素の正確な表現が格納されます。すべてのダイナミック・エンティティは %ToJSON() メソッドを使用して、格納されたデータを JSON 文字列として返すことができます。リテラル文字列への変換時やリテラル文字列からの変換時に、データが失われたり壊れたりすることはありません。次の例では、動的配列を作成してから %ToJSON() を呼び出して、格納されているデータを表す新しい JSON 文字列を構成して返します。
set dynamicArray = [[1,2,3],{"A":33,"a":"lower case"},1.23456789012345678901234,true,false,null,0,1,""]
write dynamicArray.%ToJSON()
[[1,2,3],{"A":33,"a":"lower case"},1.23456789012345678901234,true,false,null,0,1,""]
この動的配列は、いくつかの重要な値を格納して返しました。
-
最初の 2 つの要素は、入れ子になった配列と入れ子になったオブジェクトです。JSON 構文では、配列とオブジェクトの構造を任意の深さまで入れ子にすることができます。
-
プロパティ名は、大文字と小文字を区別します。この入れ子になったオブジェクトは、"A" と "a" という名前の 2 つの異なるプロパティを持っています。
-
3 つ目の値は、非常に高精度な小数です。この値が標準の浮動小数点数として格納されていた場合は、この値の端数が切り捨てられていましたが、この動的配列には元の値の正確な表現が保持されています。
-
最後の 6 つの要素には、JSON データ型の値である true、false、および null と、対応する ObjectScript 値である 0、1、および "" が格納されています。この場合でも、ダイナミック・エンティティには各値の正確な表現が保持されています。
ダイナミック式とドット構文の使用
値が JSON で格納される方法と、これらの値が ObjectScript で表現される方法の間には、大きな違いがあります。ObjectScript の値を使用しようとするたびに、その値を JSON 構文との間で変換する必要がある場合は、JSON のデータ格納はあまり便利ではありません。このため、ダイナミック・エンティティはこの変換プロセスを透過的なものにするように設計されています。JSON 構文における ObjectScript の値の表現について心配することなく、ObjectScript の値をいつでも格納および取得できます。
この規則は、リテラル JSON コンストラクタにも当てはまります。これまでに紹介したすべての例は全面的に JSON 構文に従っていましたが、リテラル・コンストラクタはダイナミック式で定義された値を受け取ることもできます。ダイナミック式は単に、括弧で囲まれた ObjectScript 式です。
例えば、次の動的配列コンストラクタは 2 つの Unicode 文字を格納します。実行時に、このリテラル・コンストラクタは各要素を評価して、評価された値を格納します。1 つ目の要素は JSON 構文で定義されており、2 つ目の要素は ObjectScript 関数呼び出しですが、結果として得られる保存値はまったく同じです。
write ["\u00E9",($CHAR(233))].%ToJSON()
["é","é"]
ObjectScript の式は、set 文の右側のコードと見なすことができます。オブジェクト参照ではなく値として評価される ObjectScript 式は、いずれも JSON リテラル文字列にシリアル化できます。以下の例では、$LIST 値 (オブジェクトではなく区切り文字列) をオブジェクト・プロパティ obj.list に格納します。次に array を作成し、obj.list の各リスト項目を別々の要素に抽出します。
set obj = {"list":($LISTFROMSTRING("Deborah Noah Martha Bowie"," "))}
set array = [($LIST(obj.list,1)),($LIST(obj.list,2)),($LIST(obj.list,3)),($LIST(obj.list,4))]
write obj.%ToJSON(),!,array.%ToJSON()
{"list":"\t\u0001Deborah\u0006\u0001Noah\b\u0001Martha\u0007\u0001Bowie"}
["Deborah","Noah","Martha","Bowie"]
ダイナミック式を使用してプロパティ名を定義することはできません (ただし、プロパティ名をプログラムによって定義するための方法が用意されています。詳細は、“%Set()、%Get()、および %Remove() の使用” を参照してください)。
当然ながら、リテラル・コンストラクタはオブジェクト・プロパティや配列要素を操作するための唯一の方法ではありません。例えば、以下のコードは空のダイナミック・オブジェクトを作成して、標準のオブジェクト・ドット構文を使用して内容を定義します。
set dynArray = []
set dynArray."0" = "200" + "33"
set dynArray."1" = {}
set dynArray."1".foo = $CHAR(dynArray."0")
write dynArray.%ToJSON()
[233,{"foo":"é"}]
この例では、リテラル・コンストラクタは空のダイナミック・エンティティを作成するためだけに使用されています。これらの代入文は、以下に示すいくつかの簡単な規則に従っています。
-
割り当てられる値は、標準の ObjectScript 式ですdynArray."0" の値は数値式として評価され、合計はキャノニック形式の整数 233 として返されます。$CHAR 関数は後でこの値を使用して ASCII 文字 233、つまり "é" を返します。
-
配列要素は配列インデックス番号によって指定されます。配列インデックス番号は、二重引用符で囲まれた数値リテラルである必要があります。動的配列は 0 から始まります。
-
オブジェクト・プロパティはプロパティ名によって指定されます。プロパティ名は文字列リテラルですが、そのプロパティ名が有効なクラス・メンバ名である場合は、二重引用符を省略可能です。
-
指定されたエンティティ・メンバがまだ存在していない場合は、そのメンバに値を割り当てたときにそのメンバが作成されます。
前述のとおり、値は、JSON 構文でどのように表現されているかにかかわらず、常に ObjectScript フォーマットで格納および取得されます。以下の例では、ドット構文の使用時に留意すべきいくつかの事項を示しています。
この例では、リテラル・コンストラクタとドット構文を使用して、A、a、および spaced name という名前のプロパティが含まれたダイナミック・オブジェクト dynObj を作成します。リテラル文字列内では、すべてのプロパティ名を引用符で囲む必要があります。set 文と write 文では、a および A というプロパティ名は引用符で囲む必要はありませんが、spaced name は引用符で囲む必要があります。
set dynObj = {"a":"change this property"}
set dynObj.a = " property a (quotes optional) "
set dynObj."spaced name" = " property ""spaced name"" must be quoted "
set dynObj.A = " property A is not property a "
write !,dynObj.%ToJSON()
{"a":" property a (quotes optional) ","spaced name":" property \"spaced name\" must be quoted ","A":" property A is not property a "}
ダイナミック・オブジェクトは箇条書きリストであるため、値は必ずしもそれらが作成された順序で格納されるわけではありません。このことを示す例については、“%GetNext() を使用したダイナミック・エンティティの反復処理” を参照してください。
動的配列は 0 から始まります。この例では、配列要素 3 に値を代入してから、要素 2 を定義します。要素は順序どおり定義される必要はないため、要素 2 を未定義のままにしておいてもかまいません。詳細は、“スパース配列と未割り当て値の理解” を参照してください。
set dynArray = [true,false]
set dynArray."3" = "three"
set dynArray."2" = 0
write dynArray.%ToJSON()
[true,false,0,"three"]
最初の 2 つの要素は、JSON ブーリアン値である true および false として定義されて格納されましたが、これらに対応する ObjectScript ブーリアン値である整数 1 および 0 として返されます。
write "0="_dynArray."0"_", 1="_dynArray."1"_", 2="_dynArray."2"_", 3="_dynArray."3"
0=1, 1=0, 2=0, 3=three
格納されている値は常に ObjectScript フォーマットで返されるため、JSON の true、false、および null という値は、ObjectScript の 0、1、および "" (空の文字列) という値として返されます。ただし、元の JSON 値はダイナミック・エンティティ内に保持されるため、必要に応じて復元できます。格納されている値の元のデータ型を特定する方法については、“データ型を使用した作業” を参照してください。
ダイナミック・オブジェクトのプロパティには任意の長さの名前を割り当てることができますが、ObjectScript では 181 文字以上のプロパティ名を使用できません。この制限を超えるダイナミック・オブジェクトのプロパティ名をドット構文で使用しようとすると、そのプロパティが存在しており、その名前が有効であるにもかかわらず、<PROPERTY DOES NOT EXIST> という誤解を招くエラー・メッセージが発行されます。このエラーを回避するには、任意の長さのプロパティ名を受け付ける %Set() メソッドと %Get() メソッドを使用します。
%Set()、%Get()、および %Remove() の使用
リテラル・コンストラクタとドット構文を使用すると、ダイナミック・エンティティ・メンバを作成して値を操作できますが、これらはすべての目的に十分に対応できるものではありません。ダイナミック・エンティティで提供されている %Set()、%Get()、および %Remove() メソッドを使用すると、作成、読み取り、更新、および削除の操作を完全にプログラム制御できます。
これらのメソッドの最も重要な利点の 1 つは、メンバ識別子 (プロパティ名や配列インデックス番号) がリテラルである必要がないことです。ObjectScript の変数と式を使用して、値と識別子の両方を指定できます。
次の例では、リテラル・コンストラクタ {} を使用してオブジェクトを作成し、この新しいオブジェクトの %Set() メソッドを呼び出して、100+n という値を持つ propn という名前の一連のプロパティを追加します。名前と値の両方が ObjectScript の式によって定義されます。
set dynObj = {}
for i=1:1:5 { do dynObj.%Set("prop"_i,100+i) }
write dynObj.%ToJSON()
{"prop1":101,"prop2":102,"prop3":103,"prop4":104,"prop5":105}
同じ変数を %Get() と共に使用して、プロパティ値を取得できます。
for i=1:1:5 { write dynObj.%Get("prop"_i)_" " }
101 102 103 104 105
%Remove() メソッドは、指定されたメンバをダイナミック・エンティティから削除し、その値を返します。この例では、5 つのプロパティのうち 3 つを削除し、戻り値を文字列 removedValues に結合します。write 文は、削除された値の文字列と dynObj の現在の内容を表示します。
set removedValues = ""
for i=2:1:4 { set removedValues = removedValues_dynObj.%Remove("prop"_i)_" " }
write "Removed values: "_removedValues,!,"Remaining properties: "_dynObj.%ToJSON()
Removed values: 102 103 104
Remaining properties: {"prop1":101,"prop5":105}
これらの簡単な例では for ループが使用されていますが、通常の反復メソッドは %GetNext() です (このメソッドについては “%GetNext() を使用したダイナミック・エンティティの反復処理” で後述します)。
%Get() と %Remove() はいずれも指定されたメンバの ObjectScript 値を返しますが、埋め込みダイナミック・エンティティが返される方法には重要な違いがあります。
-
%Get() は参照によって値を返します。戻り値は、プロパティまたは要素への OREF (オブジェクト参照) です。これには埋め込みエンティティへの参照が含まれています。
-
%Remove() は、指定されたプロパティまたは要素を破棄 (メンバ OREF を無効化) しますが、以前に埋め込まれたエンティティを直接指す、有効な OREF を返します。
次の例では、dynObj.address プロパティの値はダイナミック・オブジェクトです。%Get() 文は、変数 addrPointer にプロパティへの参照 (プロパティ値ではなく) を格納します。この時点で、addrPointer を使用して埋め込みエンティティ address の road プロパティにアクセスできます。
set dynObj = {"name":"greg", "address":{"road":"Old Road"}}
set addrPointer = dynObj.%Get("address")
set dynObj.address.road = "New Road"
write "Value of "_addrPointer_" is "_addrPointer.road
Value of 2@%Library.DynamicObject is New Road
%Remove() 文はプロパティを破棄して、新しい OREF へのプロパティ値に返します。
set addrRemoved = dynObj.%Remove("address")
write "OREF of removed property: "_addrPointer,!,"OREF returned by %Remove(): "_addrRemoved
OREF of removed property: 2@%Library.DynamicObject
OREF returned by %Remove(): 3@%Library.DynamicObject
%Remove() の呼び出し後、以前に埋め込まれたダイナミック・オブジェクトへの有効な OREF が addrRemoved に記述されます。
write addrRemoved.%ToJSON()
{"road":"New Road"}
%Remove() メソッドを使用して、メンバを任意の順序で削除できます。これは、以下の例で示すように、オブジェクトの場合と配列の場合とで異なる意味を持ちます。
オブジェクト・プロパティには固定された順序はありません。つまり、プロパティは任意の順序で破棄できますが、プロパティを削除して別のプロパティを追加すると、プロパティがシリアル化され返される順序も変更される可能性があります。以下の例では、ダイナミック・オブジェクトを作成し、%Set() の呼び出しを 3 回連続して行い、3 つのプロパティを定義します。
set dynObject={}.%Set("propA","abc").%Set("PropB","byebye").%Set("propC",999)
write dynObject.%ToJSON()
{"propA":"abc","PropB":"byebye","propC":999}
ここでは、%Remove() を呼び出してプロパティ PropB を破棄し、その後で新しいプロパティ PropD を追加します。結果のダイナミック・オブジェクトは、そのプロパティを作成順序でシリアル化しません。
do dynObject.%Remove("PropB")
set dynObject.propD = "added last"
write dynObject.%ToJSON()
{"propA":"abc","propD":"added last","propC":999}
これは、反復子のメソッド %GetNext() がプロパティを返す順序にも影響します。%GetNext() を使用する同様の例については、"%GetNext() を使用したダイナミック・エンティティの反復処理" を参照してください。
配列は 0 から始まる順序付きリストです。要素に対して %Remove() を呼び出すと、その要素のすべての後続要素の配列インデックス番号は 1 だけ小さくなります。以下の例では、%Remove(1) の呼び出しを 3 回連続して行い、毎回異なる要素を削除します。
set dynArray = ["a","b","c","d","e"]
set removedValues = ""
for i=1:1:3 { set removedValues = removedValues_dynArray.%Remove(1)_" " }
write "Removed values: "_removedValues,!,"Array size="_dynArray.%Size()_": "_dynArray.%ToJSON()
Removed values: b c d
Array size=2: ["a","e"]
通常、スタック操作は %Set() と %Remove() ではなく %Push() と %Pop() で実装されますが、%Pop() を %Remove(0) に置き換えるとキューを実装できます (“動的配列での %Push と %Pop の使用” を参照)。
%Remove() は、すべての配列と同じ方法で動作します。これには、未定義の値を持つ要素を格納する配列も含まれます。スパース配列で %Remove() がどのように機能するかを示す例は、“スパース配列と未割り当て値の理解” を参照してください。
プロパティ値としてのダイナミック・エンティティの割り当て
%Set() または %Push() を使用して、ダイナミック・エンティティを別のダイナミック・エンティティ内に入れ子にできます。例えば、ダイナミック・オブジェクトをプロパティ値または配列要素として割り当てることができます。この章の前述の例では、入れ子になったオブジェクトを取得する方法を示しました (“%Get() と %Remove() を使用した入れ子になったダイナミック・エンティティの取得” を参照)。以下の例では、入れ子になったオブジェクトを作成する 1 つの方法を示しています。
この例では、myData という名前のプロパティが指定されたダイナミック・オブジェクトが作成されます。このプロパティには、値として別のダイナミック・オブジェクトが指定されています。
{"myData":{"myChild":"Value of myChild"}}
以下のコードでこのオブジェクトが作成されます。変数として %Set() 引数を指定する必要はありませんが、指定すると、実行時に任意の有効な名前または値を割り当てることができるようになります。
set mainObj = {}
set mainPropName="myData"
set nestedObj = {}
set nestedPropName="myChild"
set nestedPropValue="Value of myChild"
do nestedObj.%Set(nestedPropName, nestedPropValue)
do mainObj.%Set(mainPropName,nestedObj)
write mainObj.%ToJSON()
このコードによって、以下のような出力が生成されます。
USER>write mainObj.%ToJSON()
{"myData":{"myChild":"Value of myChild"}}
%Set() メソッドには、いくつかの限定された状況で value 引数のデータ型を指定できるようにするオプションの type パラメータがあります (“%Set() または %Push() を使用したデフォルトデータ型のオーバーライド” を参照)。type パラメータは、value 引数がダイナミック・エンティティの場合には使用することができません。使用しようとすると、エラーがスローされます。
メソッドの連鎖
%Set() メソッドと %Push() メソッドは、これらのメソッドによって変更されたエンティティの参照を返します。返された参照を直ちに使用して、同じ式内で同じエンティティに対して別のメソッドを呼び出すことができます。
連鎖の開始元となるダイナミック・エンティティは、コンストラクタ ({} または []) であっても既存のエンティティであってもかまいません。%Set() メソッドと %Push() メソッドは連鎖可能な参照を返し、連鎖内のどこからでも呼び出し可能です。連鎖の最終要素は、そのエンティティで使用できるどのメソッドでもかまいません。
次の例では、単一の write 文で、%FromJSON()、%Set()、%Push()、および %ToJSON() の連鎖呼び出しを使用して、動的配列を作成、変更、および表示します。
set jstring = "[123]"
write [].%FromJSON(jstring).%Set(1,"one").%Push("two").%Push("three").%Set(1,"final value").%ToJSON()
[123,"final value","two","three"]
%FromJSON() は、呼び出し元エンティティの変更版を返さないため、連鎖の最初のメソッド呼び出しとしてのみ有用です。代わりに、このメソッドは呼び出し元エンティティを単に無視して、JSON 文字列から非シリアル化されたまったく新しいインスタンスを返します。詳細は、“ダイナミック・エンティティと JSON の間の変換” を参照してください。
%Get()、%Pop()、%GetNext()、または %Remove() を使用して入れ子になったエンティティを取得することによって、連鎖を開始することもできます。
エラー処理
ダイナミック・エンティティは、エラーの場合は %StatusOpens in a new tab 値を返す代わりに例外をスローします。次の例では、スローされた例外に含まれている情報から、メソッド引数の 2 つ目の文字が無効であると判断できます。
set invalidObject = {}.%FromJSON("{:}")
<THROW>%FromJSON+37^%Library.DynamicAbstractObject.1 *%Exception.General Parsing error 3 Line 1 Offset 2
動的データを扱う際は常に、一部のデータは期待に反すると想定することが推奨されます。ダイナミック・オブジェクトを利用するコードはすべて、いずれかのレベルで TRY-CATCH ブロックで囲む必要があります ("ObjectScript の使用法" の “TRY-CATCH メカニズム” を参照してください)。以下に例を示します。
TRY {
set invalidObject = {}.%FromJSON("{:}")
}
CATCH errobj {
write errobj.Name_", "_errobj.Location_", error code "_errobj.Code,!
RETURN
}
Parsing error, Line 1 Offset 2, error code 3