LoginSignup
17
13

More than 5 years have passed since last update.

Goのjson.Unmarshalでネストした子のstructを切り替えて扱う方法

Last updated at Posted at 2018-04-20

帰らなくてはならない時間ですが、時間をおいてしまうと忘れてしまうので、とっとと書いている 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用に書き換えたので動かないなどあればコメント下さい

17
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
13