はじめに (対象読者・この記事でわかること)

このブログは、Go言語で動的に受け取った interface{} 型の値を、元の具体型に復元したうえで json.Unmarshal に適用したいと考えているGo初心者から中級者を対象としています。
- interface{} から安全に型アサーションやリフレクションで元の構造体へ変換する方法が分かる
- JSON 文字列を受け取った後に、動的に生成した構造体へデシリアライズできるようになる
- エラーハンドリングや汎用的なヘルパー関数の実装例が手に入る

背景としては、外部サービスや汎用的な API ラッパーを書く際に、「型が不明なまま」 受け取ったデータをそのまま json.Unmarshal に渡すとコンパイルエラーになるケースが頻出する点があります。本記事はその課題を実践的に解決する手順を示します。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。
- Go の基本的な文法とパッケージ構成(特に encoding/json
- 型アサーションとリフレクション(reflect パッケージ)の基本的な使い方

背景と課題:interface{} から型安全に JSON をデシリアライズ

Go では、外部から受け取ったデータを interface{} に格納しておくことが多いです。例えば、汎用的な RPC クライアントや Web フレームワークのハンドラでは、リクエストボディを map[string]interface{}[]interface{} として受け取ります。このまま json.Unmarshal に渡すと、「json: cannot unmarshal object into Go value of type interface {}」 というエラーが発生します。

そこで必要になるのが 「受け取ったデータの実際の型情報を復元し、正しい構造体にマッピングした上で json.Unmarshal です。主な選択肢は次の 2 通りです。

  1. 明示的な型アサーション
    受け取る可能性が限られている場合は、value.(MyStruct) のように直接アサーションすれば済みますが、汎用性が低く、型が増えるたびにコードが肥大化します。

  2. リフレクション+ジェネリック関数
    Go 1.18 以降のジェネリックと reflect を組み合わせることで、任意の構造体型 に対して汎用的に復元できるヘルパーを作れます。これにより、コードベースがシンプルに保たれ、型安全性も確保できます。

本記事では、実務で頻繁に遭遇する「JSON を任意の構造体へデシリアライズしたい」シナリオに合わせ、リフレクションとジェネリックを活用した汎用実装 を中心に解説します。

実装手順とサンプルコード

以下では、interface{} で受け取った生データ(例: []bytemap[string]interface{})を、事前に定義した構造体型 に安全にデシリアライズするまでの流れを示します。大きく 3 つのステップに分けて解説します。

ステップ 1 ― 汎用的な型取得関数を作る

まず、任意の型 Tポインタ を生成し、reflect.New を利用して空のインスタンスを取得する関数を用意します。

Go
package jsonutil import ( "encoding/json" "reflect" ) // NewZeroValue は、型 T のゼロ値(ポインタ)を生成して返す。 // 例: NewZeroValue[MyStruct]() → *MyStruct func NewZeroValue[T any]() *T { // reflect.New は *T を返すので、そのまま型アサーションできる return reflect.New(reflect.TypeOf((*T)(nil)).Elem()).Interface().(*T) }

ポイントは reflect.TypeOf((*T)(nil)).Elem() によって T の実体型 を取得し、reflect.New でポインタを作る点です。これにより、呼び出し側は型情報だけを渡すだけでインスタンスを取得できます。

ステップ 2 ― JSON デコードを汎用化するヘルパー

次に、上記で取得したインスタンスに対して json.Unmarshal を適用する関数です。エラーハンドリングも合わせて行います。

Go
// DecodeJSON は、src が []byte または string のいずれかであることを想定し、 // 任意の型 T にデコードして返す。失敗した場合はエラーを返す。 func DecodeJSON[T any](src interface{}) (*T, error) { // 1. src を []byte に変換 var data []byte switch v := src.(type) { case []byte: data = v case string: data = []byte(v) default: // 2. map 等のケースは一度 Marshal してから Unmarshal // ただし、性能が気になる場合は別途実装した方がよい var err error data, err = json.Marshal(v) if err != nil { return nil, err } } // 3. T のゼロ値インスタンスを取得 out := NewZeroValue[T]() // 4. json.Unmarshal を実行 if err := json.Unmarshal(data, out); err != nil { return nil, err } return out, nil }

この DecodeJSONジェネリック で実装されているため、呼び出し側は次のように書くだけで型安全にデコードできます。

Go
type Person struct { Name string `json:"name"` Age int `json:"age"` } // 例: []byte で受け取った JSON jsonBlob := []byte(`{"name":"Alice","age":30}`) p, err := jsonutil.DecodeJSON[Person](jsonBlob) if err != nil { log.Fatalf("decode failed: %v", err) } fmt.Printf("名前: %s, 年齢: %d\n", p.Name, p.Age)

ステップ 3 ― interface{} を直接受け取るケースへの応用

実務では、たとえば HTTP ハンドラで interface{} にマップ化されたリクエストボディを受け取ることがあります。このときも同様に DecodeJSON が利用可能です。

Go
func handler(w http.ResponseWriter, r *http.Request) { var raw interface{} if err := json.NewDecoder(r.Body).Decode(&raw); err != nil { http.Error(w, "invalid json", http.StatusBadRequest) return } // 必要な構造体に変換 person, err := jsonutil.DecodeJSON[Person](raw) if err != nil { http.Error(w, "decode error: "+err.Error(), http.StatusUnprocessableEntity) return } // 以降は person を安全に扱える fmt.Fprintf(w, "受信した人物: %+v", person) }

ハマった点やエラー解決

1. reflect.New がポインタではなく値を返すケース

reflect.New は常に ポインタ を返すはずですが、reflect.TypeOf((*T)(nil)).Elem() の取得が正しく行われていないと reflect.New が期待外の型になることがあります。必ず Elem() を呼び出す ことで、実体型 T を取得し、ポインタ生成が正しく行えます。

2. interface{} が map[string]interface{} のまま Unmarshal できない

json.Unmarshal の第2引数は ポインタ 必須です。interface{} のまま渡すと「json: Unmarshal(non-pointer ...)」というエラーが出ます。上記の DecodeJSON では src を一度 json.Marshal してから再度 Unmarshal することで回避しています。大量データの場合は、中間マッピングを省く カスタムロジック(mapstructure 等)を検討してください。

3. ジェネリック関数での型推論失敗

呼び出し側で明示的に型パラメータを指定し忘れると、コンパイラが any 推論を行い、結果が *any になるケースがあります。必ず DecodeJSON[TargetStruct] のように型を指定 してください。

解決策まとめ

  • reflect.TypeOf((*T)(nil)).Elem()reflect.New を組み合わせて 汎用的に T のポインタを取得
  • interface{}[]byte 変換は json.Marshal を経由すれば any 型でも安全に処理
  • ジェネリック関数 DecodeJSON[T] を中心にコードを統一し、エラーハンドリングも一元化

このパターンをプロジェクト内で標準化すれば、 「型不明なデータを安全にデコード」 する作業が格段に楽になります。

まとめ

本記事では、interface{} で受け取った任意のデータを元の構造体型へ復元し、json.Unmarshal に安全に渡す方法 を解説しました。

  • reflect.New とジェネリックで 汎用的に型インスタンス を生成
  • DecodeJSON ヘルパーで JSON デコードを一行で完結
  • 実装時に遭遇しやすいエラー(型取得ミス、ポインタ不足、ジェネリック推論失敗)とその対策を提示

この手法を導入すれば、型安全性を保ちつつ柔軟な JSON ハンドリング が可能になり、コードベースの可読性と保守性が向上します。今後は、カスタムデコーダーやバリデーションロジック を組み合わせた高度なパイプライン構築についても取り上げる予定です。

参考資料