「文字列連結」という素人仕事から脱却せよ:ADO RecordsetをJSONへと昇華させる、堅牢なシリアライズ設計術
Access VBAを単なる「社内用データベース」として閉じ込めているうちは、エンジニアとしての成長は止まったも同然だ。現代の業務自動化において、Accessは「データのエンジン」であり、その出力先はWeb API、クラウドストレージ、あるいはマイクロサービスへと広がっている。
そこで直面する最大の壁が、「Recordset(レコードセット)というリレーショナルな構造を、いかにしてJSONという階層的なデータ構造へ、バグなく、高速に変換するか」という問題だ。
多くの開発者が陥る罠がある。それは、`json = json & “{“”id””:” & rs!id & “}”` といった、単純な文字列連結によるシリアライズだ。これは設計として極めて脆弱であり、データの中にダブルクォートや改行が含まれた瞬間に、生成されるJSONは破壊され、API連携はエラーを吐いて沈黙する。
本稿では、プロフェッショナルとして、どのようなデータが来ても決して壊れない、堅牢な「Recordset to JSON」のアーキテクチャを伝授する。
1. なぜDAOではなく、ADOを選択すべきなのか
議論の前に、アーキテクチャの前提を整理しておく。Access内部のテーブル操作であればDAO(Data Access Objects)で十分だ。しかし、Web API連携を見据えるなら、ADO(ActiveX Data Objects)を選択するのが定石である。
理由は、ADOが「データソースに依存しない、より抽象化されたデータモデル」を提供しているからだ。ADOのRecordsetは、カーソル制御やDisconnected Recordset(メモリ上にデータを保持する概念)の扱いにおいて、外部システムとのやり取りを意識した設計になっている。Web APIへ送るための「ペイロード(データ本体)」を構築するプロセスにおいて、ADOのフィールドオブジェクトが持つ型情報へのアクセスは、シリアライザを構築する上で極めて強力な武器となる。
2. 破綻する設計、堅牢な設計:その境界線
現場でよく見る「動いているが、いつか必ず壊れるコード」と、私が推奨する「プロダクションレベルのコード」の違いを理解してほしい。
非効率かつ脆弱な設計(Anti-Pattern)
- 型変換の無視: 数値、文字列、日付、Nullの区別をせず、すべてを文字列として結合している。
- エスケープ処理の欠如: データ内の `”` (ダブルクォート) や `\` (バックスラッシュ) を考慮していない。
- メモリ管理の放棄: ループ内で巨大な文字列を連結し続け、メモリ再割り当てのオーバーヘッドを無視している。
プロフェッショナルな設計(Best Practice)
- 型に応じた適切なシリアライズ: `Null` は `null` へ、文字列は `”` で囲み、日付は ISO 8601 形式へ。
- 完全なエスケープ処理: 特殊文字をJSON規格に従って適切に置換する。
- 動的なスキーマ対応: Recordsetのフィールド構成が変わっても、コードを書き換えずに対応できる。
3. 実装:Recordset to JSON シリアライザ
以下に、そのままプロジェクトに組み込めるレベルの、堅牢なシリアライザの実装例を示す。このコードは、Recordsetのフィールド構成を動的に解析し、型に基づいた正しいJSON配列を生成する。
※事前に「Microsoft ActiveX Data Objects x.x Library」への参照設定が必要である。
Option Explicit
''
' ======================================================================================
' Module: modJsonSerializer
' Description: ADO RecordsetをJSON文字列へ変換する、堅牢なシリアライザ
' Author: Chief Architect
' ======================================================================================
''
''' <summary>
''' ADO RecordsetをJSON形式の文字列に変換する。
''' </summary>
''' <param name="rs">変換対象のADO Recordset</param>
''' <returns>JSON形式の文字列。エラー時は空文字を返す。</returns>
Public Function RecordsetToJSON(ByRef rs As ADODB.Recordset) As String
On Error GoTo ErrorHandler
' Recordsetの状態チェック
If rs Is Nothing Then Exit Function
If rs.State = adStateClosed Then Exit Function
If rs.EOF And rs.BOF Then Exit Function
Dim jsonResult As String
Dim field As ADODB.Field
Dim row As Long
Dim fieldValues() As String
Dim i As Integer
' JSON配列の開始
jsonResult = "["
' レコードのループ
Do Until rs.EOF
jsonResult = jsonResult & "{"
' フィールドのループ(動的なスキーマ対応)
For i = 0 To rs.Fields.Count - 1
Set field = rs.Fields(i)
' キーの追加
jsonResult = jsonResult & """" & EscapeJSON(field.Name) & """:"
' 値のシリアライズ(型に応じた分岐)
jsonResult = jsonResult & SerializeValue(field.Value)
' カンマ制御
If i < rs.Fields.Count - 1 Then
jsonResult = jsonResult & ","
End If
Next i
jsonResult = jsonResult & "}"
rs.MoveNext
If Not rs.EOF Then
jsonResult = jsonResult & ","
End If
Loop
' JSON配列の終了
jsonResult = jsonResult & "]"
RecordsetToJSON = jsonResult
Exit Function
ErrorHandler:
' 実務ではログ出力クラスを呼び出すこと
Debug.Print "Error in RecordsetToJSON: " & Err.Description
RecordsetToJSON = ""
End Function
''' <summary>
''' フィールドの値の型に基づき、JSONに適した形式に変換する。
''' </summary>
Private Function SerializeValue(ByVal val As Variant) As String
' 1. Nullの処理
If IsNull(val) Then
SerializeValue = "null"
Exit Function
End If
' 2. 型判定
Select Case VarType(val)
Case vbString
' 文字列:エスケープしてダブルクォートで囲む
SerializeValue = """" & EscapeJSON(CStr(val)) & """"
Case vbBoolean
' 真偽値:true/false
SerializeValue = IIf(val, "true", "false")
Case vbInteger, vbLong, vbSingle, vbDouble, vbCurrency, vbDecimal
' 数値:そのまま(ダブルクォートは不要)
SerializeValue = CStr(val)
Case vbDate
' 日付:ISO 8601形式 (YYYY-MM-DDThh:mm:ss)
SerializeValue = """" & Format(val, "yyyy-mm-ddThh:mm:ss") & """"
Case Else
' 未知の型は文字列として扱う(安全策)
SerializeValue = """" & EscapeJSON(CStr(val)) & """"
End Select
End Function
''' <summary>
''' JSON文字列として不正な文字をエスケープする。
''' </summary>
Private Function EscapeJSON(ByVal str As String) As String
Dim res As String
res = str
' 特殊文字のエスケープ(順序が重要)
res = Replace(res, "\", "\\") ' バックスラッシュ
res = Replace(res, """", "\""") ' ダブルクォート
res = Replace(res, vbCr, "\r") ' キャリッジリターン
res = Replace(res, vbLf, "\n") ' ラインフィード
res = Replace(res, vbTab, "\t") ' タブ
EscapeJSON = res
End Function
4. アーキテクチャの要諦:なぜこのコードは「強い」のか
このコードが、単なる「動くコード」を超えて「プロダクション品質」である理由を解説する。
① 型の厳格なハンドリング
`SerializeValue` 関数に注目してほしい。VBAの `Variant` 型は非常に柔軟だが、JSONの世界では「型」が厳格だ。`Null` を単なる空文字 `””` として送ってしまうと、受け取り側のAPIで「必須項目が欠落している」と判定され、エラーになる。`IsNull` を判定し、明示的に `null` というリテラルを返す。これが、システム間連携における「契約」を守る作法だ。
② 文字列エスケープの徹底
`EscapeJSON` 関数は、このシリアライザの心臓部だ。
例えば、ユーザーの備考欄に `He said, “Hello”` という文字列が入っていた場合、エスケープなしでは `{“memo”: “He said, “Hello””}` となり、JSONとして構文エラーになる。これを `\”` に置換することで、正しい `{“memo”: “He said, \”Hello\””}` を生成する。この一手間が、システムの可用性を決定づける。
③ スキーマ・アグノスティック(スキーマに依存しない)
`rs.Fields.Count` を用いてループを回しているため、SQL文を書き換えて取得フィールドを増やしたり減らしたりしても、この関数自体を修正する必要はない。これは、保守コストを劇的に下げる設計思想である。
5. 実務における注意点:オブジェクトのライフサイクル
最後に、エンジニアとして最も重要な「メモリ管理」について触れておく。
RecordsetをJSONに変換した後、そのRecordsetオブジェクトを放置してはならない。特に、ループ内で大量のRecordsetを生成する場合、`rs.Close` を呼び出し、必ず `Set rs = Nothing` でオブジェクトを解放すること。
また、APIへ送信する際は、生成した巨大なJSON文字列をそのまま `MSXML2.XMLHTTP` 等に渡すことになるが、文字列が数十MBを超えるような場合は、VBAのメモリ限界(String型のサイズ制限)に注意が必要だ。その場合は、Recordsetを分割して処理する「チャンク(塊)処理」の検討が必要になる。
まとめ
Access VBAによる外部連携は、もはや「おまけ」ではない。
「データの型を理解し、エスケープを徹底し、動的な構造に対応する」。
この3点を守るだけで、あなたの作成するツールは、単なる「便利ツール」から、エンタープライズ環境に耐えうる「堅牢なデータエンジン」へと進化する。
さあ、文字列連結の泥沼から抜け出し、設計に基づいたコーディングを開始せよ。
