MS Projectを孤島にするな:カスタムフィールドをJSONで統合する、堅牢なデータブリッジ設計術
プロジェクトマネジメントの現場において、Microsoft Projectは最強のエンジンだ。しかし、そのエンジンが「MS Projectという閉じた箱」の中に閉じ込められている限り、真の業務自動化は達成できない。
現代のエンタープライズ環境では、プロジェクトの進捗データ(特に、標準フィールドには収まらない独自の「カスタムフィールド」)を、BIツール、Webダッシュボード、あるいは自社開発のERPシステムと連携させることが不可欠だ。その際の共通言語となるのが JSON である。
多くの開発者が陥る罠がある。それは、VBAで「文字列連結によって無理やりJSONらしきものを作る」という、極めて脆弱で保守性の低い実装だ。ダブルクォーテーション一つ、改行一つでシステムは崩壊する。
本稿では、プロフェッショナルとして、MS ProjectのカスタムフィールドをJSON形式でシリアライズ(書き出し)、およびデシリアライズ(読み込み)するための、「データモデルを分離した堅牢なアーキテクチャ」を伝授する。
1. 思考の転換:COMオブジェクトとデータ構造の分離
初心者はこう書く。
*「Taskオブジェクトをループして、その中のText1プロパティをJSON文字列に書き込む」*
これは設計思想として致命的な欠陥がある。MS Projectのオブジェクトモデル(COM)と、外部連携用のデータ構造(JSON)を密結合させてしまうからだ。これでは、MS Projectの仕様変更や、カスタムフィールドのインデックス変更に耐えられない。
我々が目指すべきは、DTO (Data Transfer Object) パターンの採用だ。
1. Extraction層: MS Projectから必要な値を抽出し、純粋なVBAの `Dictionary` または `Collection` に詰め替える。
2. Serialization層: 詰め替えたデータをJSON文字列へ変換する(ここでは定評のある `VBA-JSON` ライブラリを前提とする)。
3. Integration層: JSONを外部へ送出、あるいは受信する。
この分離により、MS Projectの複雑なライフサイクルから、データの「意味」を解放することができる。
2. 実装戦略:堅牢なエクスポート・ロジック
まず、準備として [VBA-JSON](https://github.com/VBA-tools/VBA-JSON) をプロジェクトに導入しておくこと。これはもはや、VBAにおける標準装備と言っても過言ではない。
以下に、タスクのカスタムフィールドを、意味のあるキー(Key)を持つJSONとして出力する、プロダクション品質のコード構造を示す。
プロダクションコード例:Task Data Exporter
' --- Module: modProjectIntegration ---
Option Explicit
''' <summary>
''' プロジェクト内のタスク情報を、カスタムフィールド定義に基づきJSON形式でエクスポートする
''' </summary>
''' <param name="mapping">カスタムフィールドのインデックスと、JSONでのキー名の対応辞書</param>
''' <returns>JSON文字列</returns>
Public Function ExportTasksToJSON(ByVal mapping As Object) As String
Dim proj As MSProject.Application
Set proj = MSProject.Application
Dim taskList As Object
Set taskList = CreateObject("Scripting.Dictionary")
Dim t As MSProject.Task
Dim taskData As Object
Dim key As Variant
' 高速化のための定石:画面更新停止
proj.ScreenUpdating = False
On Error GoTo ErrorHandler
' 全タスクをループ
For Each t In proj.ActiveProject.Tasks
' 空のタスク(サマリータスクではない、あるいは削除済みの残骸)をスキップ
If Not t Is Nothing Then
Set taskData = CreateObject("Scripting.Dictionary")
' 基本情報の格納
taskData("ID") = t.ID
taskData("Name") = t.Name
taskData("Start") = Format(t.Start, "yyyy-mm-dd")
taskData("Finish") = Format(t.Finish, "yyyy-mm-dd")
' --- カスタムフィールドの動的マッピング ---
' mapping引数には、例として { "Text1": "ProjectPhase", "Number1": "RiskScore" } を渡す
For Each key In mapping.Keys
taskData(CStr(mapping(key))) = GetCustomFieldValue(t, CStr(key))
Next key
' タスクリストに追加
taskList(CStr(t.ID)) = taskData
End If
Next t
' JSONへのシリアライズ(VBA-JSONライブラリを使用)
' JsonConverter.ConvertToJson は内部で適切にエスケープ処理を行う
ExportTasksToJSON = JsonConverter.ConvertToJson(taskList)
CleanUp:
proj.ScreenUpdating = True
Exit Function
ErrorHandler:
MsgBox "Export Error: " & Err.Description, vbCritical
Resume CleanUp
End Function
''' <summary>
''' カスタムフィールドの型に応じた値の取得(型安全性の確保)
''' </summary>
Private Function GetCustomFieldValue(ByVal t As MSProject.Task, ByVal fieldName As String) As Variant
' fieldName は "Text1", "Number1", "Date1" 等を想定
' ここでは単純化しているが、実際にはフィールド型に応じた厳密なハンドリングが必要
On Error Resume Next
Select Case Left(fieldName, 4)
Case "Text"
GetCustomFieldValue = t.Text(CStr(Right(fieldName, 1))) ' Text1, Text2...
Case "Numbe"
GetCustomFieldValue = t.Number(CStr(Right(fieldName, 1)))
Case "Date"
GetCustomFieldValue = Format(t.Date(CStr(Right(fieldName, 1))), "yyyy-mm-dd")
Case Else
GetCustomFieldValue = Null
End Select
On Error GoTo 0
End Function
この設計の「凄み」:
1. マッピングの外部注入: `mapping` 引数によって、どのカスタムフィールドをどの名前でエクスポートするかを呼び出し側で制御できる。これにより、コードを書き換えずに「フィールドの定義変更」に対応できる。
2. 型安全なフォーマット: 日付型を `yyyy-mm-dd` に固定している。これは、後続のシステム(PythonやJavaScript)が日付をパースする際の「壊れにくさ」を担保するためだ。
3. エラーハンドリング: COMオブジェクトの操作は、タスクの削除やフィルタリングによって予期せぬエラーを吐く。`On Error GoTo` による制御は、自動化ツールにおいて必須の作法である。
3. インポートにおける「整合性」の守護
エクスポートよりも遥かに危険なのが、JSONからのインポートだ。外部システムから送られてきたデータが、MS Projectの既存データと矛盾する場合、プロジェクト全体のスケジュールが破壊される。
インポート時には、以下の3層のガードを設けるべきだ。
1. Schema Validation: JSONの構造が正しいか(必要なキーが存在するか)。
2. Data Integrity Check: `ID` が実在するか、日付が論理的に正しいか(開始日 > 終了日になっていないか)。
3. Transaction-like Behavior: 途中でエラーが発生した場合、書き換えを中断するか、あるいはロールバックできる設計にする(VBAには厳密なトランザクションはないが、一時的なメモリ上の構造体に書き込んでから、最後に一気に適用する手法が有効だ)。
インポートの実装イメージ
Public Sub ImportTasksFromJSON(ByVal jsonString As String, ByVal mapping As Object)
Dim taskDict As Object
Set taskDict = JsonConverter.ParseJson(jsonString)
Dim taskId As Variant
Dim t As MSProject.Task
Dim mappingKey As Variant
' トランザクション的なアプローチ:
' 1. まず全てのデータが有効かチェックするループを回す(Pre-validation)
' 2. 全てOKであれば、適用ループを回す
On Error GoTo RollbackHandler
For Each taskId In taskDict.Keys
Set t = MSProject.Application.ActiveProject.Tasks(CLng(taskId))
' JSONの各フィールドをカスタムフィールドへ書き戻す
For Each mappingKey In mapping.Keys
Dim jsonKey As String: jsonKey = mapping(mappingKey)
Dim val As Variant: val = taskDict(CStr(taskId))(jsonKey)
' 書き戻しロジック(型に応じた処理)
ApplyValueToCustomField t, mappingKey, val
Next mappingKey
Next taskId
Exit Sub
RollbackHandler:
Msg[...重要: ここでユーザーに警告し、整合性が壊れたことを伝える...]
End Sub
4. アーキテクトからの最終助言:パフォーマンスの重み
大規模プロジェクト(数千タスク規模)を扱う場合、`For Each t In Tasks` のループ内でプロパティに頻繁にアクセスすることは、致命的なパフォーマンス低下を招く。
- COMのオーバーヘッドを意識せよ: VBAからMS Projectのオブジェクトへアクセスするたびに、プロセス間通信(IPC)が発生している。可能な限り、一度取得した値はローカル変数やDictionaryに保持し、MS Projectへの書き込みは「最小限の回数」に抑えること。
- ScreenUpdating と Calculation: 書き込みループの前には必ず `Application.ScreenUpdating = False` および `Application.Calculation = xlManual`(Excel連携時)や、Projectにおける再計算の抑制を検討せよ。
まとめ
カスタムフィールドをJSONで扱うことは、単なる「データの受け渡し」ではない。それは、MS Projectという閉じた世界に、開かれたインターフェースを構築する設計行為である。
DTOパターンを用い、マッピングを抽象化し、型とバリデーションを厳格に管理する。この規律こそが、現場で「本当に使える」ツールを生み出す境界線となる。
君のコードが、単なるスクリプトで終わるのか、それとも強固なエンジニアリングの結晶となるのか。その答えは、この設計思想の採用にかかっている。
