【実務・中級編】【上級者向け】カスタムフィールドの値をJSON形式でエクスポート・インポートする – Project VBA解析バイブル

スポンサーリンク
スポンサーリンク

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パターンを用い、マッピングを抽象化し、型とバリデーションを厳格に管理する。この規律こそが、現場で「本当に使える」ツールを生み出す境界線となる。

君のコードが、単なるスクリプトで終わるのか、それとも強固なエンジニアリングの結晶となるのか。その答えは、この設計思想の採用にかかっている。

タイトルとURLをコピーしました