【IRIS】【JSON】JSON操作を簡単に!%JSON.Adaptorの使い方

今回は、ObjectScriptのオブジェクト(oref)とJSONを紐づける%JSON.Adaptorについて解説します。

※この記事は下記の方向けになります。
  • JSONからデータクラスのデータを作成したい
  • データクラスから直接JSONを作成したい

はじめに

JSONはテキストベースのデータフォーマットで、構造がとてもシンプルです。
また、直観的な操作と広範な言語サポートにより、APIやデータ交換において非常に効率的なフォーマットとして認識されています。

%JSON.Adaptorを使用する事で、ObjectScriptのデータをJSON形式に変換するのがとても容易になります。

今回は、orefのデータとJSONの連携を行う、%JSON.Adaptorについて解説致します。

サンプルは全てIRIS 2023.1.2.450.0で作成しています。

使用方法

クラス定義

%JSON.Adaptor」を継承したクラスを定義します。

Class developer.json.TestData Extends (%Persistent, %JSON.Adaptor)
{

Index prim On PatientId [ PrimaryKey ];

Property PatientId As %String(CAPTION = "患者ID");

Property Name As %String(CAPTION = "漢字氏名");

Property KanaName As %String(CAPTION = "カナ氏名");

Property Sex As %Integer(CAPTION = "性別", DISPLAYLIST = ",男性,女性,不明", VALUELIST = ",1,2,3");

Property BirthDay As %Date(CAPTION = "生年月日");
}

JSONの生成

JSONテキスト取得

データの登録前と登録後でJSON(ダイナミック・エンティティ)を生成したサンプルを作成しました。
orefに対し「%JSONExport」を実行するだけで、JSONテキストの取得が可能です。

ClassMethod makeJSON()
{
	d ##class(developer.json.TestData).%KillExtent() // データの削除
	
	s obj = ##class(developer.json.TestData).%New()
	
	, obj.PatientId = "000001"
	, obj.Name = "藤原 道長"
	, obj.KanaName = "フジワラ ミチナガ"
	, obj.Sex = 1
	, obj.BirthDay = $zdh("1900-01-01",3)
	
	s txt = obj.%JSONExport() // 登録前のJSONテキスト確認
	w txt,!
	
	d obj.%Save() // データ登録
	
	s id = obj.%Id()
	s new = ##class(developer.json.TestData).%OpenId(id)
	d new.%JSONExport() // 登録後のJSONテキスト確認
}

【関数の実行結果】
{“PatientId”:”000001″,”Name”:”藤原 道長”,”KanaName”:”フジワラ ミチナガ”,”Sex”:1,”Birt hDay”:”1900-01-01″}

{“PatientId”:”000001″,”Name”:”藤原 道長”,”KanaName”:”フジワラ ミチナガ”,”Sex”:1,”Birt hDay”:”1900-01-01″}

変数にJSONテキストで取得

戻り値でstatusが取得できるので、JSONテキストの成功可否を判定可能

ClassMethod makeJSON()
{
	s obj = ##class(developer.json.TestData).%New()
	
	, obj.PatientId = "000001"
	, obj.Name = "藤原 道長"
	, obj.KanaName = "フジワラ ミチナガ"
	, obj.Sex = 1
	, obj.BirthDay = $zdh("1900-01-01",3)
	
	s sts = obj.%JSONExportToString(.jsonTxt)
	w jsonTxt
}

変数にJSONテキストをストリームで取得

JSONテキストをストリームで取得します。

ClassMethod makeJSON()
{
	s obj = ##class(developer.json.TestData).%New()
	
	, obj.PatientId = "000001"
	, obj.Name = "藤原 道長"
	, obj.KanaName = "フジワラ ミチナガ"
	, obj.Sex = 1
	, obj.BirthDay = $zdh("1900-01-01",3)
	
	s sts = obj.%JSONExportToStream(.strm)
	w strm,!
	w strm.Read()
}

【関数の実行結果】
1@%Library.FileCharacterStream

{“PatientId”:”000001″,”Name”:”藤原 道長”,”KanaName”:”フジワラ ミチナガ”,”Sex”:1,”Birt hDay”:”1900-01-01″}

JSONからorefへ変換

「ストリーム」と「JSON(ダイナミック・エンティティ)」の両方から各プロパティにデータを充てることが可能です。

ClassMethod makeJSON()
{
	s json = {
		"PatientId":"000001",
		"Name":"藤原 道長",
		"KanaName":"フジワラ ミチナガ゙",
		"Sex":1,
		"BirthDay":($zdh("1900-01-01",3))
	}
	
	s strm = ##class(%Stream.GlobalCharacter).%New()
	d json.%ToJSON(strm)
	
	
	// ストリームからの変換
	s new = ##class(developer.json.TestData).%New()
	d new.%JSONImport(strm)
	
	w new.Name,!
	
	
	// json(ダイナミック・エンティティ)からの変換
	s json = {
		"PatientId":"000002",
		"Name":"紫式部",
		"KanaName":"ムラサキシキブ",
		"Sex":2,
		"BirthDay":($zdh("1901-12-31",3))
	}
	s oref = ##class(developer.json.TestData).%New()
	d oref.%JSONImport(json)
	
	w oref.Name
}

【関数の実行結果】
藤原 道長
紫式部

特殊ケース

特殊なデータ型のJSON化

リレーションを貼ったorefがJSON化した場合、どのような構造になるか確認してみます。

【親】

Class developer.json.TestData Extends (%Persistent, %JSON.Adaptor)
{

Index prim On PatientId [ PrimaryKey ];

Property PatientId As %String(CAPTION = "患者ID");

Property Name As %String(CAPTION = "漢字氏名");

Property Wife As list Of %List(CAPTION = "妻");

Property Tale As %List(CAPTION = "物語");

Property Comment As %Stream.GlobalCharacter(CAPTION = "コメント");

Property Seria As developer.json.ChildData2;

Property SeriaList As list Of developer.json.ChildData2;

Relationship Child As developer.json.ChildData [ Cardinality = children, Inverse = Parent ];}

【子(リレーション用)】

Class developer.json.ChildData Extends (%Persistent, %JSON.Adaptor)
{

Index prim On (Parent, Name) [ PrimaryKey ];

Relationship Parent As developer.json.TestData [ Cardinality = parent, Inverse = Child ];

Property Name As %String;

Property Sex As %Integer(CAPTION = "性別", DISPLAYLIST = ",男性,女性,不明", VALUELIST = ",1,2,3");
}

【子(シリアル用)】

Class developer.json.ChildData2 Extends (%SerialObject, %JSON.Adaptor)
{

Index prim On Name [ PrimaryKey ];

Property Name As %String;

Property Sex As %Integer(CAPTION = "性別", DISPLAYLIST = ",男性,女性,不明", VALUELIST = ",1,2,3");
}

【サンプルPG】

ClassMethod makeJSON()
{
	d ##class(developer.json.TestData).%KillExtent()
	
	s oref = ##class(developer.json.TestData).%New()
	, oref.PatientId = "000001"
	, oref.Name = "藤原 道長"
	
	// $Listの配列
	d oref.Wife.Insert($lb("源", "倫子"))
	d oref.Wife.Insert($lb("源", "明子"))
	// $List
	s oref.Tale = $lb("源氏物語","紫式部")
	
	d oref.Comment.Write("コメント")
	
	// リレーション
	s cObj = ##class(developer.json.ChildData).%New()
	, cObj.Name = "藤原 頼通"
	, cObj.Sex = 1
	d oref.Child.Insert(cObj)
	
	s cObj = ##class(developer.json.ChildData).%New()
	, cObj.Name = "藤原 彰子"
	, cObj.Sex = 2
	d oref.Child.Insert(cObj)
	
	// シリアル
	s cObj = ##class(developer.json.ChildData2).%New()
	, cObj.Name = "藤原 顕信"
	, cObj.Sex = 1
	s oref.Seria = cObj
	
	// シリアル複数
	s cObj = ##class(developer.json.ChildData2).%New()
	, cObj.Name = "藤原 教通"
	, cObj.Sex = 1
	d oref.SeriaList.Insert(cObj)
	
	s cObj = ##class(developer.json.ChildData2).%New()
	, cObj.Name = "藤原 威子"
	, cObj.Sex = 2
	d oref.SeriaList.Insert(cObj)
	
	// JSONテキスト取得
	d oref.%JSONExportToString(.jsonTxt)
	w jsonTxt,!!
	
	// ダイナミック・エンティティ取得
	s json = {}.%FromJSON(jsonTxt)
	
	s new = ##class(developer.json.TestData).%New()
	d new.%JSONImport(json)
	
	d new.%JSONExportToString(.newText)
	w newText,!
}

【関数の実行結果】
{“PatientId”:”000001″,”Name”:”藤原 道長”,”Wife”:[“源,倫子”,”源,明子”],”Tale”:” 源氏物語,紫式部”,”Comment”:”コメント”,”Seria”:{“Name”:”藤原 顕信”,”Sex”:1},”Ser iaList”:[{“Name”:”藤原 教通”,”Sex”:1},{“Name”:”藤原 威子”,”Sex”:2}],”Child”:[{ “Name”:”藤原 頼通”,”Sex”:1},{“Name”:”藤原 彰子”,”Sex”:2}]}

{
  ”PatientId”:”000001″,
  ”Name”:”藤原 道長”,
  ”Wife”:[
    ”源,倫子”,
    ”源,明子”
  ],
  ”Tale”:”源氏物語,紫式部”,
  ”Comment”:”コメント”,
  ”Seria”:{
    ”Name”:”藤原 顕信”,
    ”Sex”:1
  },
  ”SeriaList”:[
    {
      ”Name”:”藤原 教通”,
      ”Sex”:1
    },
    {
      ”Name”:”藤原 威子”,
      ”Sex”:2
    }
  ],
  ”Child”:[
    {
      ”Name”:”藤原 頼通”,
      ”Sex”:1
    },
    {
      ”Name”:”藤原 彰子”,
      ”Sex”:2
    }
  ]
}

出力された結果より、下記が確認できます。

  • リレーションとシリアライズは、JSONの構成が同じ仕様となる
  • リレーションやシリアライズされた子は、配列(%DynamicArray)の子として構築される
  • $Listは、$listToStringを実施した状態で構築される
  • list of 型を指定すると、配列(%DynamicArray)として構築される

パラメータを使用してマッピングする

外部APIとの連携時、JSONのパラメータとプロパティ名が一致しないケースが想定できます。
そのような場合の回避策が、パラメータ「%JSONFIELDNAME」を利用したマッピングです。

【マッピング対象のクラス】

Class developer.json.ChangeName Extends (%RegisteredObject, %JSON.Adaptor)
{

/// プロパティ変換メソッドの生成可否
Parameter %JSONENABLED = 1;

/// 空値の処理 1:"", 0:$c(0), null
Parameter %JSONIGNORENULL = 0;

/// 未指定のプロパティの対応
Parameter %JSONNULL = 1;

/// オブジェクトの表現 OBJECT, ID, OID, GUID
Parameter %JSONREFERENCE = "OID";

/// 予期しないフィールドをエラーにする
Parameter %JSONIGNOREINVALIDFIELD = 0;

Property PatientId As %String(%JSONFIELDNAME = "bango");

Property Name As %String(%JSONFIELDNAME = "shimei");

/// Property Sex As %String(%JSONFIELDNAME = "seibetsu", %JSONINCLUDE = "inout");
Property Sex As %String(%JSONFIELDNAME = "seibetsu", %JSONINCLUDE = "inputonly");

Property BirthDay As %Date(%JSONFIELDNAME = "tanjobi", %JSONINCLUDE = "outputonly");

Property Secret As %String(%JSONINCLUDE = "none");

Property Job As %String(%JSONFIELDNAME = "yakushoku");

Property int As %Integer;

/// オブジェクトの確認
Property Oref As developer.data.Patient2;

}

【サンプルコマンド】

ClassMethod covName()
{
	s json = {
		"Date":"test",
		"bango": "000001",
		"shimei":"藤原 道長",
		"seibetsu": "男",
		"tanjobi" : ($zdh("1900-01-01",3)),
		"Secret": "秘密"
	}
	s oref = ##class(developer.json.ChangeName).%New()
	, oref.Oref = ##class(developer.data.Patient2).%OpenId(1)
	
	// Import
	s sts = oref.%JSONImport(json)
	w oref.Sex,! // 性別の確認
	w $system.Status.DisplayError(sts),!!
	
	d oref.%JSONExportToString(.jtxt)
	w jtxt,!!
	
	
	// Export
	s oref.BirthDay = $zdh("1900-01-01",3)
	, oref.Secret = "秘密"

	d oref.%JSONExportToString(.jtxt)
	w jtxt,!
}

【関数の実行結果】

エラー #9404: class base マッピングを使用して予期しないフィールドの入力 Date です。1

{“bango”:”000001″,”shimei”:”藤原 道長”,”tanjobi”:null,”yakushoku”:null,”int”:nu ll,”Oref”:”developer.data.Patient2,1″}

{“bango”:”000001″,”shimei”:”藤原 道長”,”tanjobi”:”1900-01-01″,”yakushoku”:null, “int”:null,”Oref”:”developer.data.Patient2,1″}

解説

先ず抑えるポイントは下記2点で、プロパティのみに設定が可能です。

パラメータ説明
%JSONFIELDNAMEプロパティ名とは別のフィールド名で、値の入出力を行う
%JSONINCLUDEマッピングの入出力時に制限を行う
入力時のみ:inputOnly、出力時のみ:outputOnly、両方:none

後は全体 or 個別で制御を行う設定が下記になります。

パラメータ説明
%JSONENABLED0を設定すると入出力が行えなくなる(初期値:1)
%JSONIGNORENULL1を設定すると出力時「””」となる(初期値:0)
%JSONNULL未指定のプロパティを出力する(初期値:0)
%JSONREFERENCEobjectの出力設定[object, ID, OID, GUID](初期値:object)
%JSONIGNOREINVALIDFIELD0:入力時に予期しないフィールドをエラーとする(初期値:0)

XDataを使用してマッピングする

PropertyやParameterにマッピング情報を書き込むと、可読性が下がる事に懸念がある場合は、XDataにマッピング情報を設定する方法があります。

このマッピング方法であれば、データクラスの構造とマッピングを切り離す事が可能です。

また、XDataにマッピングしていないプロパティは、入出力を行わない特徴があります。

【マッピング対象のクラス】

Class developer.json.ChangeXData Extends (%RegisteredObject, %JSON.Adaptor)
{

Property PatientId As %String;

Property Name As %String;

Property Sex As %String;

Property BirthDay As %Date;

Property Secret As %String;

Property Job As %String;

Property int As %Integer;

/// オブジェクトの確認
Property Oref As developer.data.Patient2;

XData ChangeName
{
<Mapping xmlns="http://www.intersystems.com/jsonmapping" Null="1" IgnoreInvalidField="0" IgnoreNull="0" Reference="OID">
	<Property Name="PatientId" FieldName="bango" />
	<Property Name="Name" FieldName="shimei" />
	<Property Name="Sex" FieldName="seibetsu" Include="inputonly" />
	<Property Name="BirthDay" FieldName="tanjobi" Include="outputonly" />
	<Property Name="Secret" Include="NONE" />
	<Property Name="Job" FieldName="yakushoku" />
	<Property Name="int" />
	<Property Name="Oref" />
	</Mapping>
}

}

【サンプルコマンド】

ClassMethod covNameXData()
{
	s json = {
		"Date":"test",
		"bango": "000001",
		"shimei":"藤原 道長",
		"seibetsu": "男",
		"tanjobi" : ($zdh("1900-01-01",3)),
		"Secret": "秘密"
	}
	s oref = ##class(developer.json.ChangeXData).%New()
	, oref.Oref = ##class(developer.data.Patient2).%OpenId(1)
	
	// Import
	s sts = oref.%JSONImport(json, "ChangeName")
	w oref.Sex,!
	w $system.Status.DisplayError(sts),!
	
	d oref.%JSONExportToString(.jtxt, "ChangeName")
	w jtxt,!
	
	
	// Export
	s oref.BirthDay = $zdh("1900-01-01",3)
	, oref.Secret = "秘密"

	d oref.%JSONExportToString(.jtxt, "ChangeName")
	w jtxt,!
}

【関数の実行結果】

エラー #9404: class base マッピングを使用して予期しないフィールドの入力 Date です。1

{“bango”:”000001″,”shimei”:”藤原 道長”,”tanjobi”:null,”yakushoku”:null,”int”:nu ll,”Oref”:”developer.data.Patient2,1″}

{“bango”:”000001″,”shimei”:”藤原 道長”,”tanjobi”:”1900-01-01″,”yakushoku”:null, “int”:null,”Oref”:”developer.data.Patient2,1″}

各設定パラメータに関しては、「%JSON」を付与した元の名前に沿っています。
 例)Null → %JSONNULL
   FieldName → %JSONFIELDNAME

おわりに

%JSON.Adaptorは、単なるJSON変換ツールではなく、柔軟で高度な機能を備えています。
プロパティマッピングやXDataマッピング等を活用する事で、API連携がより効率的なものになります。

本記事で紹介した内容を活用し、快適なJSONライフを堪能してください。