【テクニカル・上級編】ADOのRecordsetをJSON形式に変換する:Web API連携を見据えたデータ構造の変換術 – Access VBA解析バイブル

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

[Architect’s Note] Access VBA: レガシー・エンジンをモダン・エコシステムへ接続する「ADO-to-JSON」変換の真髄

Accessという技術を「古い」と切り捨てる者は、その真の力を理解していない。Accessは、ローカル環境におけるデータ操作の極致であり、強力なRDBMSエンジン(ACE/JET)を内包した、極めて高密度なアプリケーション・プラットフォームだ。

しかし、現代のシステム設計において、Accessを単なる「スタンドアロンのデータストア」として閉じ込めておくことは、戦略的な敗北を意味する。Web API、マイクロサービス、クラウドストレージ。これらのモダンな世界とAccessを接続する際、我々に求められるのは、単なるデータの転送ではない。「型」と「構造」の厳密な変換、そしてメモリ管理の徹底である。

本稿では、ADO(ActiveX Data Objects)のRecordsetを、Web API連携に耐えうる高精度なJSON形式へとシリアライズするための、アーキテクチャレベルの知見を提示する。

1. 境界線上の設計思想:なぜDAOではなくADOなのか

Access開発において、DAO(Data Access Objects)は「家の中の住人」であり、ADOは「外の世界との交渉人」である。

DAOは、Accessエンジンに対して最適化されており、ローカルなクエリ実行においては圧倒的なパフォーマンスを誇る。しかし、ADOはCOM(Component Object Model)の柔軟性を活用し、CursorLocationの制御や、Disconnected Recordsetといった、ネットワーク越し、あるいはメモリ上でのデータ操作に特化した設計となっている。

Web APIとの連携を見据えるならば、選択肢は一つ、ADOである。ADOのRecordsetは、データの型情報をより抽象化し、外部システムとのインターフェースとして扱うための「設計の自由度」を持っているからだ。

2. 陥りやすい罠:素朴な文字列結合の限界

多くの開発者が、Recordsetのループ内で `json = json & “””” & field.Name & “””: “”” & field.Value & “””,”` といったコードを書く。これは、プロフェッショナルの仕事ではない。

この手法には、以下の致命的な欠陥がある:
1. エスケープ処理の欠如: 文字列内に `”` や `\` が含まれた瞬間、生成されるJSONは壊れたゴミと化す。
2. Null値の誤認: VBAの `Null` は、JSONの `null` とは異なる。不用意な変換は、API側での型エラーを誘発する。
3. パフォーマンスの劣化: 文字列の反復的な結合(Concatenation)は、メモリの再確保を繰り返すため、データ量が増大した際に指数関数的に処理速度を低下させる。
4. データ型の欠落: Date型やBoolean型が、適切なISO 8601形式や真偽値として出力されない。

3. 実装:高精度シリアライザーの構築

以下に示すのは、メモリ効率と堅牢性を両立させた、クラスモジュールによる実装例である。ここでは、文字列結合のコストを抑えるため、`StringBuilder` 的なアプローチを意識し、かつ特殊文字のエスケープを徹底している。

クラスモジュール: `clsJsonSerializer`

' Class Module: clsJsonSerializer
Option Explicit

' ---------------------------------------------------------------------------------------
' 目的: ADO.RecordsetをJSON文字列へ高精度に変換する
' 設計思想: 
'   1. 特殊文字のエスケープを徹底し、JSONの妥当性を担保する
'   2. Null値、日付型、真偽値を適切にハンドリングする
'   3. オブジェクトのライフサイクルを意識したメモリ管理
' ---------------------------------------------------------------------------------------

Public Function SerializeRecordset(ByRef rs As ADODB.Recordset) As String
    Dim sb As Object
    Dim i As Integer
    Dim field As ADODB.Field
    Dim val As Variant
    Dim jsonStr As String
    
    ' Recordsetの状態を確認
    If rs Is Nothing Then Exit Function
    If rs.EOF And rs.BOF Then
        SerializeRecordset = "[]"
        Exit Function
    End If

    ' 文字列構築の最適化(Dictionary等はオーバーヘッドが大きいため、
    ' 大規模データではString変数による構築か、Win32 APIの活用を検討する)
    sb = CreateObject("System.Text.StringBuilder")
    
    sb.Append "["
    
    Do While Not rs.EOF
        If sb.Length > 1 Then sb.Append "," ' 配列の区切り
        
        sb.Append "{"
        For i = 0 To rs.Fields.Count - 1
            Set field = rs.Fields(i)
            
            ' キーの書き出し
            sb.Append """" & EscapeString(field.Name) & """:"
            
            ' 値の書き出し(型判定)
            val = field.Value
            
            If IsNull(val) Then
                sb.Append "null"
            Else
                Select Case field.Type
                    Case adInteger, adSmallInt, adLong, adSingle, adDouble, adCurrency, adDecimal
                        sb.Append val
                    Case adBoolean
                        sb.Append IIf(val, "true", "false")
                    Case adDate
                        ' ISO 8601形式への変換
                        sb.Append """" & Format(val, "yyyy-mm-ddThh:nn:ss") & """"
                    Case Else
                        ' 文字列として処理
                        sb.Append """" & EscapeString(CStr(val)) & """"
                End Select
            End If
            
            ' フィールド間のカンマ制御
            If i < rs.Fields.Count - 1 Then
                sb.Append ","
            End If
        Next i
        
        sb.Append "}"
        rs.MoveNext
    Loop
    
    sb.Append "]"
    
    SerializeRecordset = sb.ToString()
    
    ' オブジェクトの明示的な解放
    Set sb = Nothing
End Function

' ---------------------------------------------------------------------------------------
' Private Function: EscapeString
' 目的: JSONの構文を破壊する特殊文字をエスケープする
' ---------------------------------------------------------------------------------------
Private Function EscapeString(ByVal str As String) As String
    Dim result As String
    result = str
    ' 順序が重要:バックスラッシュを最初に置換しなければ、他の置換文字を誤ってエスケープする
    result = Replace(result, "\", "\\")
    result = Replace(result, """", "\""")
    result = Replace(result, vbCr, "\r")
    result = Replace(result, vbLf, "\n")
    result = Replace(result, vbTab, "\t")
    EscapeString = result
End Function

4. アーキテクトの視点:メモリとパフォーマンスの極限

上記のコードにおいて、私が `System.Text.StringBuilder` を採用した理由を説明しておこう。

VBA標準の `String` 変数への結合(`&` 演算子)は、結合のたびに新しいメモリ領域を確保し、古いデータをコピーするという、極めて非効率な動作を繰り返す。数万件のレコードを扱う場合、このオーバーヘッドは無視できない。`.NET` の `StringBuilder` を COM経由で呼び出すことで、メモリ再確保の回数を劇的に減らし、計算量を $O(n^2)$ から $O(n)$ へと引き下げている。

さらに、実務においては以下の「極限の配慮」を忘れてはならない。

オブジェクトのライフサイクル管理

VBAにおける `Set obj = Nothing` は、単なる作法ではない。COMオブジェクトの参照カウントを適切に減らし、メモリリークを防ぐための「防衛策」だ。特に、ループ内で `Field` オブジェクトや `Dictionary` を生成する場合、明示的な解放を行わなければ、Accessのメモリ使用量は際限なく膨張し、最終的に `Out of Memory` でクラッシュする。

トランザクションと一貫性

APIにデータを送る際、Recordsetの取得中にデータが更新される可能性がある。

' 読み取り専用の、かつ、整合性を担保したカーソル設定
rs.CursorLocation = adUseClient
rs.Open strSql, conn, adOpenStatic, adLockReadOnly

このように、`adUseClient` を指定することで、メモリ上にスナップショットを作成し、通信中のデータ不整合を防ぐ。これは、レガシーなシステムとモダンなAPIを繋ぐ際の、データ整合性における「鉄則」である。

5. 結論

Access VBAを単なる自動化ツールとして扱うか、それとも堅牢なエンタープライズ・ゲートウェイとして扱うか。その差は、こうした「型」と「メモリ」への敬意に現れる。

JSONへのシリアライズは、一見単純な作業に見える。しかし、エスケープ処理の順序、Nullの扱い、日付のフォーマット、そしてメモリの挙動。これらすべてにおいて、アーキテクトとしての責任を果たすことが、システムの寿命を決定づけるのである。

技術は、使いこなす者の規律によって、初めて真の価値を持つ。

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