一部可変的な構造体の要素を含むJSONをUnmarshalして可変部の値を取り出す
背景
- 以下に示すような一部データ構造が可変的な要素を含むJSONをUnmarshalしてかつその要素の値を取り出したい
- 前提として可変部のタイプはJSONに含まれるある値(ここでは
key
とする)で判定可能とする - 今回
"variable"
の要素が[]string
または[]struct
の場合を扱う
{ "list": [ { "key": "A", "value": "aaa" }, { "key": "B", "variable": [ "b-1", "b-2" ] }, { "key": "C", "variable": [ { "foo": "1", "bar": "2" }, { "foo": "3", "bar": "4" } ] } ] }
Unmarshalするための構造体の定義
type Input struct { List []Element `json:"list"` } type Element struct { Key string `json:"key"` Value string `json:"value,omitempty"` VariablePart interface{} `json:"variable,omitempty"` } type ObjectVariable struct { Foo string `json:"foo"` Bar string `json:"bar"` }
"variable"
に対応するフィールド(VariablePart)はinterface{}
型とし定義する。
今回の場合ここをjson.RawMessage
にしていないのは後で明示的に構造体を割り当てるため。
UnmarshalJSONの実装
func (e *Element) UnmarshalJSON(b []byte) error { type Alias Element a := &struct { // ここでは一旦json.RawMessageにしておく VariablePart json.RawMessage `json:"variable"` *Alias }{ Alias: (*Alias)(e), } // 一旦全体をUnmarshal if err := json.Unmarshal(b, &a); err != nil { return err } // Keyに応じてVariablePartのUnmarshalする構造体を判定して明示的に割り当てる switch e.Key { case "B": var s []string if err := json.Unmarshal(a.VariablePart, &s); err != nil { log.Fatal(err) } e.VariablePart = &s case "C": var s []ObjectVariable if err := json.Unmarshal(a.VariablePart, &s); err != nil { log.Fatal(err) } e.VariablePart = &s default: return nil } return nil } const input = `最初に書いたjson` func main() { var i Input if err := json.Unmarshal([]byte(input), &i); err != nil { log.Fatal(err) } }
UnmarshalJSON()は関連付けた構造体がUnmarshalされる際に呼び出されるメソッドで、今回の場合はElement構造体がUnmarshalされる際に呼び出され、keyに応じてUnmarshalする構造体を判定してUnmarshalしElement.VariablePartに割り当てています
可変部の値を取り出す
// 原則としてBはjson内に1つしか含まれない func (i *Input) GetVariableB() []string { as := []string{} for _, e := range i.List { if e.Key != "B" { continue } switch e.VariablePart.(type) { case *[]string: as = *e.VariablePart.(*[]string) } break } return as } // 原則としてCはjson内に1つしか含まれない func (i *Input) GetVariableC() []ObjectVariable { ov := []ObjectVariable{} for _, e := range i.List { if e.Key != "C" { continue } switch e.VariablePart.(type) { case *[]ObjectVariable: ov = *e.VariablePart.(*[]ObjectVariable) } break } return ov }
やり方は色々あると思いますが、今回はくどいですが念の為keyの値を確認しつつ型もチェックした上で要素を返すメソッドを実装しました
func main() { ... // Unmarshalした可変部の値を取得 ss := i.GetVariableB() for _, s := range ss { log.Printf("%q", s) } lo := i.GetVariableC() for _, o := range lo { log.Printf("%+v", o) } }
$ go run main.go "b-1" "b-2" {Foo:1 Bar:2} {Foo:3 Bar:4}
まとめ
- 今回可変部のあるJSONに対してその可変部の値を取り出したかった
- 可変部に応じた構造体を明示的にUnmarshalするためElement構造体用のUnmarshalJSON()に処理を実装した
- さらにその可変部の値を取り出す専用のメソッドを定義することで値を取り出せるようにしてみた
参考
- この記事書いた後に見つけてしまった、4年前に書かれたほぼほぼ内容が似ている記事
- json.RawMesage fieldを使用したUnmarshalについて