Caché での JSON の使用
ダイナミック・エンティティの作成と変更
[Home] [Back] [Next]
InterSystems: The power behind what matters   
Class Reference   
Search:    

この章では、ダイナミック・エンティティが機能する仕組みについての基本的な情報を提供します。以下の項目について説明します。

JSON リテラル・コンストラクタの使用
ダイナミック・エンティティは、%DynamicObject または %DynamicArray のインスタンスです。これらのクラスは、JSON のデータ操作を Caché にシームレスに統合するように設計されています。標準の %New() メソッドを使用してこれらのクラスのインスタンスを作成できますが、ダイナミック・エンティティは、これよりはるかに柔軟で直感的な一連のコンストラクタをサポートしています。JSON リテラル・コンストラクタを使用すると、JSON 文字列を変数に直接代入することでダイナミック・エンティティを作成できます。例えば、以下のコードは %DynamicObject%DynamicArray の空のインスタンスを作成します。
   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 リテラル文字列はすべて、評価結果がダイナミック・エンティティとなる有効な Caché 式でもあります。
注意:
JSON プロパティ名は常に引用符で囲む必要があります。
JSON 言語仕様 (http://json.org/ を参照) は 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,""]
この動的配列は、いくつかの重要な値を格納して返しました。
ダイナミック式とドット構文の使用
値が JSON で格納される方法と、これらの値が ObjectScript で表現される方法の間には、大きな違いがあります。ObjectScript の値を使用しようとするたびに、その値を JSON 構文との間で変換する必要がある場合は、JSON のデータ格納はあまり便利ではありません。このため、ダイナミック・エンティティはこの変換プロセスを透過的なものにするように設計されています。JSON 構文における ObjectScript の値の表現について心配することなく、ObjectScript の値をいつでも格納および取得できます。
この規則は、リテラル JSON コンストラクタにも当てはまります。これまでに紹介したすべての例は全面的に JSON 構文に従っていましたが、リテラル・コンストラクタはダイナミック式で定義された値を受け取ることもできます。ダイナミック式は単に、括弧で囲まれた ObjectScript 式です。
例えば、次の動的配列コンストラクタは 2 つの Unicode 文字を格納します。実行時に、このリテラル・コンストラクタは各要素を評価して、評価された値を格納します。1 つ目の要素は JSON 構文で定義されており、2 つ目の要素は Caché 関数呼び出しですが、結果として得られる保存値はまったく同じです。
   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" = "02"_"33"
   set dynArray."1" = {}
   set dynArray."1".foo = $CHAR(dynArray."0")
   write dynArray.%ToJSON()

[233,{"foo":"é"}]
この例では、リテラル・コンストラクタは空のダイナミック・エンティティを作成するためだけに使用されています。これらの代入文は、以下に示すいくつかの簡単な規則に従っています。
前述のとおり、値は、JSON 構文でどのように表現されているかにかかわらず、常に ObjectScript フォーマットで格納および取得されます。以下の例では、ドット構文の使用時に留意すべきいくつかの事項を示しています。
ドット構文を使用したダイナミック・オブジェクト・プロパティの作成
この例では、リテラル・コンストラクタとドット構文を使用して、Aa、および C quote という名前のプロパティが含まれたダイナミック・オブジェクト dynObj を作成します。リテラル文字列内では、すべてのプロパティ名を引用符で囲む必要があります。set 文と write 文では、a および A というプロパティ名は引用符で囲む必要はありませんが、C quote は引用符で囲む必要があります。
   set dynObj = {"a":"stuff"}
   set dynObj."C quote" = " ""C quote"" contains a space "
   set dynObj.a = " lower case ""a"" "
   set dynObj.A = " upper case ""A"" "
   write !,dynObj.%ToJSON()

{"a":" lower case \"a\" ","C quote":" \"C quote\" contains a space ","A":" upper case \"A\" "}
ダイナミック・オブジェクトは箇条書きリストであるため、値は必ずしもそれらが作成された順序で格納されるわけではありません。このことを示す例については、%GetNext() を使用したダイナミック・エンティティの反復処理 を参照してください。
ドット構文を使用した動的配列要素の作成
この例では、配列要素 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 として定義されて格納されましたが、これらに対応する Caché ブーリアン値である整数 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 の truefalse、および null という値は、Caché の 01、および "" (空の文字列) という値として返されます。ただし、元の JSON 値はダイナミック・エンティティ内に保持されるため、必要に応じて復元できます。格納されている値の元のデータ型を特定する方法については、データ型を使用した作業 を参照してください。
注意:
ドット構文は、非常に長いプロパティ名で使用しないでください。
ダイナミック・オブジェクトのプロパティには任意の長さの名前を割り当てることができますが、ObjectScript では 181 文字以上のプロパティ名を使用できません。この制限を超えるダイナミック・オブジェクトのプロパティ名をドット構文で使用しようとすると、そのプロパティが存在しており、その名前が有効であるにもかかわらず、Caché は <PROPERTY DOES NOT EXIST> という誤解を招くエラー・メッセージを発行します。このエラーを回避するには、任意の長さのプロパティ名を受け付ける %Set() メソッドと %Get() メソッドを使用します。
%Set()、%Get()、および %Remove() の使用
リテラル・コンストラクタとドット構文を使用すると、ダイナミック・エンティティ・メンバを作成して値を操作できますが、これらはすべての目的に十分に対応できるものではありません。ダイナミック・エンティティで提供されている %Set()%Get()、および %Remove() メソッドを使用すると、作成、読み取り、更新、および削除の操作を完全にプログラム制御できます。
これらのメソッドの最も重要な利点の 1 つは、メンバ識別子 (プロパティ名や配列インデックス番号) がリテラルである必要がないことです。ObjectScript の変数と式を使用して、値と識別子の両方を指定できます。
%Set()、%Get()、および %Remove() を使用したプログラムによる値と識別子の指定
次の例では、リテラル・コンストラクタ {} を使用してオブジェクトを作成し、この新しいオブジェクトの %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() はいずれも指定されたメンバの Caché ObjectScript 値を返しますが、埋め込みダイナミック・エンティティが返される方法には重要な違いがあります。
%Get() と %Remove() を使用した入れ子になったダイナミック・エンティティの取得
次の例では、dynObj.address プロパティの値はダイナミック・オブジェクトです。%Get() 文は、変数 addrPointer にプロパティへの参照 (プロパティ値ではなく) を格納します。この時点で、addrPointer を使用して埋め込みエンティティ addressroad プロパティにアクセスできます。
   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() の呼び出し後、addrRemoved には以前に埋め込まれたダイナミック・オブジェクトへの有効な OREF が含まれ、変数 addrPointer にはプロパティ dynObj.address の前の場所への無効な OREF が含まれます。
   write addrRemoved.%ToJSON()

{"road":"New Road"}

   write addrPointer.%ToJSON()

WRITE addrPointer.%ToJSON()
^
<INVALID OREF>
%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() を使用したダイナミック・エンティティの反復処理" を参照してください。
配列要素の削除
配列は順序付きリストです。要素に対して %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() メソッドは、これらのメソッドによって変更されたエンティティの参照を返します。返された参照を直ちに使用して、同じ式内で同じエンティティに対して別のメソッドを呼び出すことができます。
連鎖の開始元となるダイナミック・エンティティは、コンストラクタ ({} または []) であっても既存のエンティティであってもかまいません。%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() を使用して入れ子になったエンティティを取得することによって、連鎖を開始することもできます。
エラー処理
ダイナミック・エンティティは、エラーの場合は %Status 値を返す代わりに例外をスローします。次の例では、スローされた例外に含まれている情報から、メソッド引数の 2 つ目の文字が無効であると判断できます。
   set invalidObject = {}.%FromJSON("{:}")

<THROW>%FromJSON+37^%Library.DynamicAbstractObject.1 *%Exception.General Parsing error 3 Line 1 Offset 2
動的データを扱う際は常に、一部のデータは期待に反すると想定することが推奨されます。ダイナミック・オブジェクトを利用するコードはすべて、いずれかのレベルで TRY-CATCH ブロックで囲む必要があります ("Caché 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
ダイナミック・エンティティと JSON の間の変換
%ToJSON() メソッドを使用してダイナミック・エンティティをシリアル化 (JSON 文字列に変換) でき、%FromJSON() メソッドを使用して非シリアル化 (JSON をダイナミック・エンティティに変換) できます。
ダイナミック・エンティティから JSON へのシリアル化
次の例では、ダイナミック・オブジェクトを作成して変更してから、%ToJSON() を使用してこのダイナミック・オブジェクトをシリアル化して、結果として得られる文字列を表示します。
   set dynObject={"prop1":true}.%Set("prop2",123).%Set("prop3","foo")
   set objString = dynObject.%ToJSON()
   write objString

{"prop1":true,"prop2":123,"prop3":"foo"}
動的配列は同じ方法でシリアル化されます。
   set dynArray=[].%Push("1st value").%Push("2nd value").%Push("3rd value")
   set arrayString = dynArray.%ToJSON()
   write arrayString

["1st value","2nd value","3rd value"]
これらの両方の例ではメソッドの連鎖を使用しています (本章で前出の メソッドの連鎖 を参照してください)。
JSON からダイナミック・オブジェクトへの非シリアル化
%FromJSON() メソッドは、JSON 文字列をダイナミック・エンティティに変換します。次の例では、動的配列を作成して、この動的配列を jstring という文字列にシリアル化します。%FromJSON() を呼び出すことによって、jstringnewArray という名前の新しいダイナミック・エンティティに非シリアル化された後、このダイナミック・エンティティが変更されて表示されます。
   set jstring=["1st value","2nd value","3rd value"].%ToJSON()
   set newArray={}.%FromJSON(jstring)
   do newArray.%Push("new value")
   write "New entity:"_newArray.%ToJSON()

New entity:["1st value","2nd value","3rd value","new value"]
この例では、返される値は動的配列ですが、%FromJSON() がダイナミック・オブジェクト・コンストラクタ ({}) から呼び出されることに注目してください。%FromJSON()%DynamicAbstractObject のクラス・メソッドであるため、任意のダイナミック・エンティティまたはコンストラクタから呼び出すことができます。
%ToJSON() と %FromJSON() を使用した複製
%FromJSON() を呼び出すたびに新しいダイナミック・エンティティが作成されるため、このメソッドを使用して、既存のエンティティを複製したり一連の同一エンティティを初期化したりできます。
次の例では、dynObj.address プロパティの値はダイナミック・オブジェクトです。このプロパティは変数 addrPointer によって参照され、このプロパティの値は、%FromJSON() を呼び出して新しいダイナミック・オブジェクト addrClone を作成することによって複製されます。
   set dynObj = {}.%FromJSON({"name":"greg", "address":{"road":"Dexter Ave."}}.%ToJSON())
   set addrPointer = dynObj.address 
   set addrClone = {}.%FromJSON(dynObj.address.%ToJSON())
変数 addrPointer はプロパティ dynObj.address の単なる参照ですが、addrClone は、元の値に影響を与えることなく変更可能な %DynamicObject の独立したインスタンスです。
   set addrPointer.road = "Wright Ave."
   set addrClone.road = "Sinister Ave."
   write !,"Property = "_dynObj.address.%ToJSON(),!,"Clone = "_addrClone.%ToJSON()

Property = {"road":"Wright Ave."}
Clone = {"road":"Sinister Ave."}
大きいダイナミック・エンティティからストリームへのシリアル化
ダイナミック・エンティティが非常に大きい場合は、%ToJSON() の出力は Caché 文字列の最大許容長を超える可能性があります ("Caché プログラミング入門ガイド" の 長い文字列の制限 を参照してください)。このセクションの例では、longStr という名前の最大長の Caché 文字列を使用しています。以下のコード例は、longStr の生成方法を示しています。
   set longStr=""
   for i=1:1:$SYSTEM.SYS.MaxLocalLength() { set longStr = longStr_"x" }
   write "Maximum string length = "_$LENGTH(longStr)

Maximum string length = 3641144
式で %ToJSON() の返り値が使用されるたびに、Caché はプログラム・スタック上にその文字列を構築します (この文字列には長い文字列の制限が適用されます)。例えば、write dyn.%ToJSON() などの読み取り/書き込み文や、set x=dyn.%ToJSON() などの代入文は、その文字列をスタック上に配置しようとします。次の例では、longStr の 2 つのコピーを動的配列に追加して、このシリアル化された文字列を変数に代入しようとしており、その結果として Caché は <MAXSTRING> エラーを返しています。
   set longArray = [(longStr),(longStr)]
   set tooBig = longArray.%ToJSON()
SET tooBig = longArray.%ToJSON()
^
<MAXSTRING>
この問題の一般的な解決策は、返り値を実際に調べることなく、DO コマンド内の参照によって %ToJSON() の出力を渡すことです。出力は現在のデバイスに直接書き込まれるため、出力の長さに制限はありません。次の例では、デバイスはストリームです。
ファイル・ストリームへの書き込み
この例では、ダイナミック・オブジェクト longObject をファイルに書き込んでから、このダイナミック・オブジェクトを取得します。変数 longStr は、このセクションの冒頭で定義した値です。
   set longObject = {"a":(longStr),"b":(longStr)}
   set file=##class(%File).%New("c:\temp\longObjectFile.txt")
   do file.Open("WSN")
   do longObject.%ToJSON(file)
   do file.Close()

   do file.Open("RS")
   set newObject = {}.%FromJSON(file)
   write !,"Property newObject.a is "_$LENGTH(newObject.a)_" characters long."

Property newObject.a is 3641144 characters long.
この解決策を使用して、他のストリームから入力を読み取ることもできます。
グローバル文字ストリームの読み取りと書き込み
この例では、2 つの大きいダイナミック・エンティティをシリアル化します (%ToJSON() はストリームごとに 1 つのエンティティしかシリアル化できないため、一時ストリームを使用しています)。標準のストリーム処理メソッドを使用して、各一時ストリームを別々の行としてストリーム bigLines に格納します。
   set tmpArray = ##class(%Stream.GlobalCharacter).%New()
   set dyn = [(longStr),(longStr)]
   do dyn.%ToJSON(tmpArray)

   set tmpObject = ##class(%Stream.GlobalCharacter).%New()
   set dyn = {"a":(longStr),"b":(longStr),"c":(longStr)}
   do dyn.%ToJSON(tmpObject)

   set bigLines = ##class(%Stream.GlobalCharacter).%New()
   do bigLines.CopyFrom(tmpArray)
   do bigLines.WriteLine()
   do bigLines.CopyFrom(tmpObject)
後で、bigLines から各ダイナミック・エンティティを非シリアル化できます。
   do bigLines.Rewind()
   while ('bigLines.AtEnd) { 
      write !,{}.%FromJSON(bigLines.ReadLineIntoStream())
   }

7@%Library.DynamicArray
7@%Library.DynamicObject