@ka2n

Technology and beer

Go言語で埋め込みをしたstructでのjson.UnmarshalJSON

goを書いていて、部分実装やゆるふわなJSONを扱うためにstructの埋め込みを使うことがある。 そのstructに対してJSONを読み込む時に少しハマった事があるのでメモ。

まずはじめに、こんな具合でJSNONをOuterマッピングしていた。

https://play.golang.org/p/zGo779zhxI

package main

import (
    "encoding/json"
    "fmt"
)

type Outer struct {
    InnerA
    InnerB
}

type InnerA struct {
    FieldA string `json:"a_field"`
}

type InnerB struct {
    FieldB string `json:"b_field"`
}

func main() {
    var v Outer
    data := []byte(`{"a_field": "A", "b_field": "B"}`)
    if err := json.Unmarshal(data, &v); err != nil {
        fmt.Println("[Error] " + err.Error())
        return
    }
    fmt.Printf("%+v", v)
        // {InnerA:{FieldA:A} InnerB:{FieldB:B}}
}

開発中にJSONb_fieldが文字でないfalseを返す場合があることが分かってしまったので以下の様に対応した。

https://play.golang.org/p/0WszSKVN0m

package main

import (
    "encoding/json"
    "fmt"
)

type Outer struct {
    InnerA
    InnerB
}

type InnerA struct {
    FieldA string `json:"a_field"`
}

type InnerB struct {
    FieldB string `json:"b_field"`
}

func (e *InnerB) UnmarshalJSON(data []byte) error {
    var v struct {
        FieldB interface{} `json:"b_field"`
    }
    if err := json.Unmarshal(data, &v); err != nil {
        return nil
    }

    switch v.FieldB.(type) {
    case string:
        break
    default:
        return nil
    }

    e.FieldB = v.FieldB.(string)
    return nil
}

func main() {
    var v Outer
    data := []byte(`{"a_field": "A", "b_field": "B"}`)
    if err := json.Unmarshal(data, &v); err != nil {
        fmt.Println("[Error] " + err.Error())
        return
    }
    fmt.Printf("%+v", v)
        // => {InnerA:{FieldA:} InnerB:{FieldB:B}}
}

これで上手くいくと思いきや、InnerAのフィールドがゼロ値になってしまっている。 そこで、面倒だけどOuterでそれぞれUnmarshalすることでなんとかなった。

https://play.golang.org/p/kii9ZmoX1O

package main

import (
    "encoding/json"
    "fmt"
)

type Outer struct {
    InnerA
    InnerB
}

type InnerA struct {
    FieldA string `json:"a_field"`
}

type InnerB struct {
    FieldB string `json:"b_field"`
}

func (e *Outer) UnmarshalJSON(data []byte) error {
    var err error
    err = json.Unmarshal(data, &e.InnerA)
    if err != nil {
        return err
    }
    err = json.Unmarshal(data, &e.InnerB)
    if err != nil {
        return err
    }
    return nil

}

func (e *InnerB) UnmarshalJSON(data []byte) error {
    var v struct {
        FieldB interface{} `json:"b_field"`
    }
    if err := json.Unmarshal(data, &v); err != nil {
        return nil
    }

    switch v.FieldB.(type) {
    case string:
        break
    default:
        return nil
    }

    e.FieldB = v.FieldB.(string)
    return nil
}

func main() {
    var v Outer
    data := []byte(`{"a_field": "A", "b_field": "B"}`)
    if err := json.Unmarshal(data, &v); err != nil {
        fmt.Println("[Error] " + err.Error())
        return
    }
    fmt.Printf("%+v", v)
}

理由は後で調べる。