Go で構造の一部が動的に変わる JSON を扱いたい

Naomichi Agata
Jan 3, 2018 · 4 min read

json.RawMessage を使うと,一部のフィールドを見てから payload の型を決定することができます

問題

WebSocket でやりとりするサーバを書いていて,一つのコネクション上でいくつかの種類のコマンドを JSON として受け付けるような仕組みが欲しくなりました.

{
"action": "increment",
"payload": {
"value": 3
}
}

{
"action": "greet",
"payload": {
"name": "World"
"language": "English"
}
}

のように, action フィールドに応じて payload の構造が変わるという構成です.

golang でふつうに JSON を受け取って構造体にマップする際には,下のようにします.

type Message struct {
Action string `json:"action"`
Payload struct {
Value int `json:"value"`
} `json:"payload"
}
var msg Message
json.Unmarshal(body, &msg)

この方法では, action の値に応じて payload の構造が変わることを表現できません.無理やり Payload の struct を大きくして

struct {
Value int `json:"value"`
Name string `json:"name"`
Language string `json:"language"`
}

のようにすることも出来ますが,どの action の時にどのフィールドが使えるのかわかりにくい上に, value のような汎用的な名前のフィールドが複数の型を取るケースを表現できません.( { "value": 1 }{ "value": "World" } など)

解決方法

json.RawMessage を使って,動的な部分の unmarshal を遅延します.

type Message struct {
Action string `json:"action"`
Payload json.RawMessage `json:"payload"`
}
var msg Message
json.Unmarshal(body, &msg)

このようにすると, msg.Payload には []byte(`{"value": 1}`) がそのまま残ります.

そこで,それぞれの action ごとの payload 型を定義しておき

type IncrementPayload struct {
Value int `json:"value"`
}
type GreetPayload struct {
Name string `json:"name"`
Language string `json:"language"`
}

msg.Action に応じて, msg.Payload をどう unmarshal するか決定します.

switch msg.Action {
case "increment":
var p IncrementPayload
json.Unmarshal(msg.Payload, &p)
// do increment action
case "greet":
var p GreetPayload
json.Unmarshal(msg.Payload, &p)
// do greet action
}

まとめ

  • 動的に構造が決定する JSON を受け付けたかった
  • json.RawMessage を使うと,一部のフィールドだけ unmarshal 操作を遅延できる
  • 構造の決定に必要な部分だけ先に unmarshal して,そこを見てから残りを unmarshal する

動的に構造が決定する部分を何かしらの interface として抽象化できる場合は,特にこの手法が有効そうです.

そもそも { "action": "increment", "increment_payload": { "value": 1 } }{ "action": "greet", "greet_payload": { "name": "world", "language": "English" } } のようにフィールド名を変えてしまうという手もありますね.

)

Naomichi Agata

Written by

Software engineer, interested in programming languages.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade