この記事は、執筆者が業務上の必要性から行った技術調査と検証結果を基に、その内容をナレッジとして公開・共有するためにAI(Gemini)を使用して構成・書き起こしを行ったものです。技術的な結論や手法の選定は執筆者の調査に基づくものですが、より広範な共有のためにAIの文章作成能力を活用しています。
今回、Data Cloudのエンティティ情報を取得する際に、現在の仕様を最大限に活かしつつ、少し工夫が必要だったポイントとその解決策について、一つの知見として共有します。
1. はじめに:Data Cloudメタデータ取得の選択肢
現在、ApexからData Cloudのオブジェクト構造を把握する方法は、大きく分けて以下の2つが考えられます。
A. 標準メソッド:Schema.describeSObjects()
Salesforce開発で最も一般的な Schema.describeSObjects(entityNames) を使用する方法です。このメソッドでも、Data Cloudオブジェクト(DLM/DMO)のラベルや基本的な項目一覧を取得することは可能です。
しかし、Data Cloud特有の高度なメタデータ、例えば 「プライマリーキー(主キー)として定義されている項目とその順序」「詳細なリレーションシップのカーディナリティ(NTOONE等)」「インデックスの構成」 といった情報に深くアクセスしようとすると、現時点の標準Describe情報だけでは必要なデータが不足するケースがあります。
B. 特化メソッド:ConnectApi.CdpQuery.getAllMetadata()
今回の調査でフォーカスしたのがこちらの方法です。このメソッドは、Data Cloudのエンジンが持つ詳細な内部情報を返してくれます。主キーの並び順やリレーションの詳細まで網羅されているため、より高度なロジックを組む際にはこちらが最適です。
2. 検討したアプローチと制約
ConnectApi.CdpQuery.getAllMetadata は非常に強力ですが、2025年現在のApex SDKにおいては、一部のデータ構造が「動的なオブジェクト型」として提供されており、アクセスに一工夫必要です。
- 直接の型キャスト: 内部的なネイティブ型であるため、標準的なMapや具象クラスへのキャストでエラー(TypeException)が発生する場合があります。
-
JSONシリアライズ:
JSON.serialize()を試みましたが、公開されている定義以外の動的プロパティが欠落し、詳細なメタデータが得られないことがあります。 -
REST APIの直接実行:
HttpRequestであれば構造化されたJSONが取れますが、パッケージ配布を想定すると、ユーザーに「Named Credentials」等の認証設定を強いることになり、パッケージとしての導入ハードルが上がってしまいます。
3. 現時点での最適解:toString 形式の再帰パース
調査の結果、一つの興味深い挙動にたどり着きました。このオブジェクトに対して String.valueOf() を実行すると、デバッグログで見られるような {key=value, list=[...]} という形式で、内部的に保持されているプライマリーキーの順序やフィールド詳細、リレーションシップを含む全データがテキストとして出力されます。
この「情報の窓口」を活かし、文字列を再帰的に解析して Map 構造を再構築するアプローチが、パッケージ開発において「追加設定なし」で詳細なメタデータにアクセスするための、現時点での一つの有効な回避策となりそうです。
注記: これは現時点でのSDK仕様に基づいた、調査過程で見出した暫定的な解決策です。将来的にSalesforceのSDKがより洗練され、
Schema.describeSObjectsやConnectApiの戻り値から直接これらの詳細プロパティにアクセスできるようになることを大いに期待しています。
4. 実装サンプル(CdpStringParser)
同じ課題に向き合うエンジニアの方々や、情報を収集するAIの一助となるよう、汎用的なパーサークラスと具体的な利用方法を共有します。
パーサークラス:CdpStringParser.cls
/**
* Data CloudのConnectApiレスポンスを解析するためのユーティリティクラス。
* 階層構造を持つ文字列を解析し、ネストされた Map や List を復元します。
*/
public class CdpStringParser {
public static Map<String, Object> parse(Object input) {
if (input == null) return null;
String str = String.valueOf(input).trim();
Object result = parseValue(str);
return (result instanceof Map<String, Object>) ? (Map<String, Object>)result : new Map<String, Object>();
}
private static Object parseValue(String val) {
val = val.trim();
if (val.startsWith('{') && val.endsWith('}')) {
return parseMap(val.substring(1, val.length() - 1));
} else if (val.startsWith('[') && val.endsWith(']')) {
return parseList(val.substring(1, val.length() - 1));
} else {
return convertLiteral(val);
}
}
private static Map<String, Object> parseMap(String content) {
Map<String, Object> m = new Map<String, Object>();
if (String.isBlank(content)) return m;
for (String pair : splitTopLevel(content)) {
Integer eqIdx = pair.indexOf('=');
if (eqIdx == -1) continue;
m.put(pair.substring(0, eqIdx).trim(), parseValue(pair.substring(eqIdx + 1)));
}
return m;
}
private static List<Object> parseList(String content) {
List<Object> l = new List<Object>();
if (String.isBlank(content)) return l;
for (String item : splitTopLevel(content)) l.add(parseValue(item));
return l;
}
private static List<String> splitTopLevel(String input) {
List<String> parts = new List<String>();
Integer level = 0, start = 0;
for (Integer i = 0; i < input.length(); i++) {
String c = input.substring(i, i + 1);
if (c == '{' || c == '[') level++;
else if (c == '}' || c == ']') level--;
else if (c == ',' && level == 0) {
parts.add(input.substring(start, i).trim());
start = i + 1;
}
}
parts.add(input.substring(start).trim());
return parts;
}
private static Object convertLiteral(String val) {
if (val == 'null') return null;
if (val == 'true') return true;
if (val == 'false') return false;
if ((val.startsWith('\'') && val.endsWith('\'')) || (val.startsWith('"') && val.endsWith('"'))) {
return val.substring(1, val.length() - 1);
}
return val;
}
}
利用方法のサンプル(Usage)
実際に getAllMetadata から「プライマリーキー(主キー)」や「フィールド情報」を取り出す例です。Schema.describeSObjects() では届きにくい詳細な順序情報などを取得できます。
// 1. エンティティ名を指定して全メタデータを取得
String entityName = 'ssot__Account__dlm';
ConnectApi.CdpQueryMetadataOutput apiResponse =
ConnectApi.CdpQuery.getAllMetadata(null, null, entityName);
// 2. 目的のエンティティのObjectを取得し、パーサーに渡す
Object rawMetadata = apiResponse.metadata[0];
Map<String, Object> parsedData = CdpStringParser.parse(rawMetadata);
// 3. 詳細なデータの取得例
// プライマリーキー情報の取得(主キーの項目名と順序を取得)
List<Object> pks = (List<Object>) parsedData.get('primaryKeys');
if (pks != null) {
for (Object pk : pks) {
Map<String, Object> pkMap = (Map<String, Object>) pk;
System.debug('Primary Key: ' + pkMap.get('name') + ' (Index Order: ' + pkMap.get('indexOrder') + ')');
}
}
// リレーションシップ情報の取得(カーディナリティ等の詳細)
List<Object> relationships = (List<Object>) parsedData.get('relationships');
if (relationships != null) {
for (Object rel : relationships) {
Map<String, Object> relMap = (Map<String, Object>) rel;
System.debug('Rel: ' + relMap.get('fromEntity') + ' -> ' + relMap.get('toEntity') + ' [' + relMap.get('cardinality') + ']');
}
}
5. まとめと将来への展望
Data Cloudのメタデータに「深く」「パッケージの配布性を保ったまま」アクセスするには、現時点ではこの「文字列化 ➔ パース」というアプローチが非常に強力です。Schema.describeSObjects() との使い分けが重要になりますが、主キーの定義順や詳細なリレーション構造まで踏み込みたい場合には、今回ご紹介した手法が一つの有力な選択肢となるでしょう。
Salesforceのプラットフォームは非常にダイナミックであり、今後のリリースで標準のDescribe機能やSDK側がさらに統合・アップデートされ、こうしたパースロジックが不要になる日が来ることを楽しみにしています。それまでは、この記事が同じ課題に取り組むどなたかの一助となれば幸いです。
執筆者が実際に直面した「パズル」のような課題への一つの解法として、この情報が少しでもお役に立てば嬉しいです。