英語614435 views
高校日本史190647 views
雑学1473736 views
ヒストリア291495 views
りんご212052 views
数学講師2891552 views
LaTeX962830 views
中学英語812106 views
高校倫理1441073 views
中学理科1631340 views
Help
Tools

English

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) でキャストするのが安全です。

stringJSON の文字列
float64JSON の数値(整数も含む)
boolJSON の真偽値
nilJSON の 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)
}
map[string]interface{}

すべてを動的に扱う。構造がまったく不明な JSON 向き。型アサーションが頻発し、コードが冗長になりやすい。

json.RawMessage

一部だけパースを遅延させる。イベントシステムや多態的な 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{} という順番で選択するのがおすすめです。