問題の歴史:
Goにはデフォルトのジェネリックマップのような汎用コンテナがありません。Go 1.18からはジェネリクスが導入されましたが、動的構造に関しては、以前から現在までmap[string]interface{}がよく使用され、文字列キーに対して任意の型の値を保持できます。
問題:
このパターンは辞書やJSONオブジェクトの類似物で、シリアル化、ミドルウェアでの操作、特定の構造を持たないデータ(例えば、encoding/jsonによるJSONパーサー)に頻繁に見られます。ただし、値へのアクセスは手動の型変換が必要であり、暗黙の値やキーの欠如に対して注意が必要です。
解決策:
入力データの構造が不明な場合はmap[string]interface{}を使用します。キーの存在を注意深く確認し、exists-idiomの後のみ型を変換(type assertion)します。このようなマップをビジネスロジックに「深く」保持することは避け、システム境界でのアダプターとして使用する方が良いでしょう。
コード例:
obj := map[string]interface{}{ "int": 42, "str": "こんにちは", "flag": true, } if v, ok := obj["int"]; ok { n, success := v.(int) if success { fmt.Println(n) } }
主な特徴:
キーが存在しない場合、map[string]interface{}でキーの値にエラーなくアクセスできますか?
いいえ、これはinterface{}に対して「ゼロ値」(nil)を返し、特定の型への変換でパニックを引き起こします。
map[string]interface{}をネストしたスライスや他のmapとシリアル化するとどうなりますか?
JSONシリアル化は構造を正しく処理しますが、デフォルトでサポートされていない型(チャネル、関数など)が含まれる場合、マーチャル化エラーが発生します。
map[string]interface{}の二つの値を==で比較できますか?
いいえ、interface{}は基になる値が比較可能な場合にのみ比較できます。mapやスライスが含まれると、比較時にパニックになります。
アプリケーションのロジックがすべてmap[string]interface{}オブジェクトに基づいており、各コントローラー/サービスがそれを深く呼び出しに渡す。
利点:
欠点:
外部インターフェース、入出力データの操作にのみmap[string]interface{}を使用し、その後正常な構造に変換します。
利点:
欠点: