Go で動的な JSON を扱う:map[string]interface{} と json.RawMessage
JSON の構造が事前にわからない場合や、キーが動的に変化する場合、構造体では対応しきれません。そんなときに使えるのが map[string]interface{} と json.RawMessage です。
map[string]interface{} で自由に受け取る
構造が不定な JSON をパースするもっとも手軽な方法は、map[string]interface{} に Unmarshal することです。
package main
import (
"encoding/json"
"fmt"
)
func main() {
data := `{"name":"Alice","age":30,"active":true}`
var result map[string]interface{}
json.Unmarshal([]byte(data), &result)
fmt.Println(result["name"]) // Alice
fmt.Println(result["age"]) // 30
fmt.Println(result["active"]) // true
}キーが string、値が interface{} なので、どんな JSON でも受け取れます。ただし値の型はすべて interface{} になるため、使うときに型アサーションが必要です。
name, ok := result["name"].(string)
if !ok {
fmt.Println("name は文字列ではありません")
}
age, ok := result["age"].(float64)
if !ok {
fmt.Println("age は数値ではありません")
}JSON の数値は Go では常に float64 に変換される点に注意してください。整数のつもりで int にアサーションするとパニックが起きます。float64 で受け取ってから int(age) でキャストするのが安全です。
| string | JSON の文字列 |
| float64 | JSON の数値(整数も含む) |
| bool | JSON の真偽値 |
| nil | JSON の null |
| map[string]interface{} | JSON のオブジェクト |
| []interface{} | JSON の配列 |
map の限界
map[string]interface{} は万能に見えますが、実際に使うとコードが煩雑になります。毎回型アサーションが必要で、ネストが深くなるほど読みづらくなるのです。
// ネストした JSON を map で扱う例
data := `{"user":{"address":{"city":"Tokyo"}}}`
var result map[string]interface{}
json.Unmarshal([]byte(data), &result)
// 深いネストへのアクセスが冗長
user := result["user"].(map[string]interface{})
address := user["address"].(map[string]interface{})
city := address["city"].(string)3 段階のネストですでにこの有様です。JSON の構造がある程度わかっているなら、構造体を定義したほうが安全で読みやすいコードになります。map[string]interface{} は「構造がまったく予測できない」場合の最終手段と考えておきましょう。
json.RawMessage で一部だけ遅延パースする
JSON の一部は構造体で受け取りたいが、残りの部分は後でパースしたい、という場面では json.RawMessage が使えます。
type Event struct {
Type string `json:"type"`
Payload json.RawMessage `json:"payload"`
}
func main() {
data := `{"type":"user_created","payload":{"name":"Alice","email":"alice@example.com"}}`
var event Event
json.Unmarshal([]byte(data), &event)
fmt.Println(event.Type) // user_created
// event.Payload はまだ生の JSON バイト列
}json.RawMessage は []byte のエイリアスで、Unmarshal 時にパースされずそのまま保持されます。Type フィールドの値を見てから、Payload を適切な構造体にパースし直すことが可能です。
switch event.Type {
case "user_created":
var user struct {
Name string `json:"name"`
Email string `json:"email"`
}
json.Unmarshal(event.Payload, &user)
fmt.Printf("ユーザー作成: %s (%s)\n", user.Name, user.Email)
case "order_placed":
var order struct {
ItemID int `json:"item_id"`
Amount int `json:"amount"`
}
json.Unmarshal(event.Payload, &order)
fmt.Printf("注文: 商品 %d を %d 個\n", order.ItemID, order.Amount)
}すべてを動的に扱う。構造がまったく不明な JSON 向き。型アサーションが頻発し、コードが冗長になりやすい。
一部だけパースを遅延させる。イベントシステムや多態的な API レスポンスに最適。構造体の型安全性を維持しつつ柔軟に対応できる。
json.RawMessage で Marshal する
json.RawMessage は Unmarshal だけでなく Marshal にも使えます。事前に構築した JSON バイト列をそのまま埋め込みたい場合に便利です。
type Response struct {
Status string `json:"status"`
Data json.RawMessage `json:"data"`
}
func main() {
rawData := json.RawMessage(`{"items":[1,2,3]}`)
resp := Response{
Status: "ok",
Data: rawData,
}
output, _ := json.Marshal(resp)
fmt.Println(string(output))
// {"status":"ok","data":{"items":[1,2,3]}}
}Data フィールドは再エンコードされず、生の JSON がそのまま出力に含まれます。外部 API のレスポンスをそのまま中継するプロキシ的な用途で役立ちます。
動的な JSON を扱う場面では、まず構造体で表現できないか検討し、それが難しい場合に json.RawMessage、最終手段として map[string]interface{} という順番で選択するのがおすすめです。











