LoginSignup
6

More than 1 year has passed since last update.

Go で JSON を取り扱う際のプラクティス集

Last updated at Posted at 2020-12-16

これは Go2 Advent Calender 2020 の 17 日目の記事です。

Go は encoding/json という JSON を取り扱うための公式 package を提供しており、
簡単に JSON を取り扱うことができます。
今回は、その package を使って JSON を取り扱う際の代表的なプラクティスをまとめました。

ターゲットとする読者

本記事がターゲットとする読者は以下の通りです。

  • 今から Go で JSON をこねこねしようと思っている人
  • Go で JSON を扱ったことがあるけど、プラクティス集があると振り返れていいなと思う人

Practice 1: 構造体との Unmarshal/Marshal

以下は一番基本の JSON の decode/encode のスタイルです。
JSON に対応した構造体を定義し、その構造体へ JSON を decode/encode します。
API 名としては、decode = Unmarshal、encode = Marshal として表現されています。

Unmarshal

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

func main() {
    type Dog struct {
        DogBreed string
        Name     string
        Age      string
    }
    jsonBlob := []byte(`{"DogBreed": "Shiba", "Name": "Taro", "Age": "1"}`)

    var dog Dog

    err := json.Unmarshal(jsonBlob, &dog)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%v\n", dog)
}
$ go build -o a.exe && a.exe
{Siba Taro 1}

Marshal

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

func main() {
    type Dog struct {
        DogBreed string
        Name     string
        Age      string
    }
    dog := Dog{DogBreed: "Shiba", Name: "Taro", Age: "1"}

    var jsonBlob []byte

    jsonBlob, err := json.Marshal(&dog)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%v\n", string(jsonBlob))
}
$ go build -o a.exe && a.exe
{"DogBreed":"Shiba","Name":"Taro","Age":"1"}

tips

  • JSON の key と構造体のメンバ名は一致している必要がある
    • JSON の key と異なる名前を付ける場合は、JSON tag を付与する (この記事では説明していない)
  • 構造体のメンバは export (CamelCase に) する必要がある

Practice 2: Unmarshal 方法のバリエーション

Practice 1 の方法は定番の方法ではあるものの、構造体を定義したりやや面倒な方法ではあります。
Unmarshal/Marshal は *interface{} を渡せる仕様となっているため、以下の様に記述することができます。

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

func main() {
    jsonBlob := []byte(`{"DogBreed": "Shiba", "Name": "Taro", "Age": "1"}`)

    var dog interface{}

    err := json.Unmarshal(jsonBlob, &dog)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%v\n", dog)
}
$ go build -o a.exe && a.exe
map[Age:1 DogBreed:Shiba Name:Taro]

tips

  • *interface{} を渡した場合、map 型で Unmarshal 結果が返ってくる

Practice 3: 遅延デコード

Practice1、2 のコードでは、一回の encode/decode 処理で JSON を Go のデータオブジェクトへ変換していました。
しかし、それでは「ある JSON の key の値に従って、JSON の decode の仕方を変更したい」場合に上手く decode することができません。
それに対応する方法としては、以下のコードの様に、遅延デコードしたい JSON key を *json.RawMessage へ一旦保存しておく方法があります。

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

func main() {
    type Data struct {
        Type   string
        Person json.RawMessage
    }
    type PersonA struct {
        Name string
        Age  string
    }
    type PersonB struct {
        Name     string
        Birthday string
    }
    jsonStr := `[{"Type":"A", "Person":{"Name":"taro","Age":"1"}},{"Type":"B", "Person":{"Name":"Jiro","Birthday":"2020/11/15"}}]`
    var data []Data
    if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
        log.Fatal(err)
    }
    for _, d := range data {
        switch d.Type {
        case "A":
            var p PersonA
            if err := json.Unmarshal(d.Person, &p); err != nil {
                log.Fatal(err)
            }
            fmt.Printf("%+v\n", p)
        case "B":
            var p PersonB
            if err := json.Unmarshal(d.Person, &p); err != nil {
                log.Fatal(err)
            }
            fmt.Printf("%+v\n", p)
        default:
            log.Fatal("invalid type")
        }
    }
}
$ go build -o a.exe && a.exe
{Name:taro Age:1}
{Name:Jiro Birthday:2020/11/15}

tips

  • json.RawMessage 型の実体は []byte のため、その JSON byte 列に対して再度 Unmarshal をするイメージ

まとめ

  • encoding/json で大体のやりたいことは実現できる
  • loose に decode する方法についても提供されている
  • 逆に strict な key の存在チェックなどについては実装されていないため、やりたい時はサードパーティ等に頼る必要がある

References

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
What you can do with signing up
6