帰らなくてはならない時間ですが、時間をおいてしまうと忘れてしまうので、とっとと書いている chidakiyo です。こんばんは。
問題
ネストしたJSONで、子のstructのバリエーションが7パターンぐらいあるという問題に当たり、愚直にマッピングすると親7つと子7つを地道に実装することになります。
interface{}としてマッピングしてもよいのですが、ハンドリングがやたらややこしくなります。(めんどくさい)
JSONの構造的には具体的にはこんな感じ
[
{
"ID":1,
"Name":"A",
"Child":[
{ // この子要素のフィールドがtypeにより可変
"Desc1":"d1",
"Desc2":"d2",
"Type":1}]
},
{
"ID":2,
"Name":"B",
"Child":[]
}
]
親要素の中に子要素があり、子要素が持っているtypeで7パターンの構造体を振り分ける、のようなイメージです。
Childのフィールドをstructやinterfaceでマッピングしなければ良いじゃない
というわけで、 Child
のフィールドはマッピングしなければ良いじゃない、というわけで string 型にしてみます。
struct的にはこんな感じですね
type Hoge struct {
ID int64
Name string
Child string
}
コードはこんな感じ
type Hoge struct {
ID int64
Name string
Child string
}
var jbyte = []byte(`[{"ID":1,"Name":"A","Child":[{"Desc1":"d1","Desc2":"d2","Type":1}]},{"ID":2,"Name":"B","Child":[]}]`)
s := &[]Hoge{}
if err := json.Unmarshal(jbyte, s); err != nil {
t.Fatalf("errorだよ %s", err.Error())
}
これを実行すると。。。
json: cannot unmarshal array into Go struct field Hoge.Child of type string
という感じに怒られます。。。
こまった。
正解は[]byteにマッピングすれば良い
題の通り。
ちょうど、jsonパッケージに json.RawMessage
というのがあります。
実装は
// RawMessage is a raw encoded JSON value.
// It implements Marshaler and Unmarshaler and can
// be used to delay JSON decoding or precompute a JSON encoding.
type RawMessage []byte
こんな感じなので []byte ですね。
で、先程のコードを以下のように修正します。
type Hoge struct {
ID int64
Name string
Child json.RawMessage
}
var jbyte = []byte(`[{"ID":1,"Name":"A","Child":[{"Desc1":"d1","Desc2":"d2","Type":1}]},{"ID":2,"Name":"B","Child":[]}]`)
s := &[]Hoge{}
if err := json.Unmarshal(jbyte, s); err != nil {
t.Fatalf("errorだよ %s", err.Error())
}
t.Logf("%v\n", string((*s)[0].Child))
を実行すると
[{"Desc1":"d1","Desc2":"d2","Type":1}]
となりいい感じですね。
最初にあった謎の要件を満たす。
要件は
親要素の中に子要素があり、子要素が持っているtypeで7パターンの構造体を振り分ける
という感じなので、子要素のtypeを判別する必要があります。
子要素のtypeはすべての子要素が共通して持つので
type TypeOnly struct {
Type int64
}
のように実装できます。
先程のコードにtype判別のための実装を含めると
type Hoge struct {
ID int64
Name string
Child json.RawMessage
}
var jbyte = []byte(`[{"ID":1,"Name":"A","Child":[{"Desc1":"d1","Desc2":"d2","Type":1}]},{"ID":2,"Name":"B","Child":[]}]`)
s := &[]Hoge{}
if err := json.Unmarshal(jbyte, s); err != nil {
t.Fatalf("errorだよ %s", err.Error())
}
brunch := (*s)[0].Child
ss := &[]TypeOnly{}
if err := json.Unmarshal(brunch, ss) ; err != nil {
t.Fatalf("やっぱりerrorだねぇ。。。。 %s", err.Error())
}
t.Logf("%v\n", (*ss)[0].Type)
のような感じで実装すればログに 1
と出力され、typeが判別できます。
あとはこれをswitch文なりで分岐して、brunchの[]byteをそれぞれの構造体を実装しUnmarshalすれば良いと思います。
(最後はだいぶ手抜き)
さいごに
手元のちょっとした作業の検証用に使ったコードをQiita用に書き換えたので動かないなどあればコメント下さい