GoでJSONを扱う方法を調べてみた

GW何をしようかと考えた末に、久しぶりにGoを触ってみようかなと思いまして、JSONの使い方を調べてみました。(Goを触るのが1年半ぶりくらいなので、A Tour of Goをやり直しました。Goはドキュメントが非常によく整備されていて助かります。)

ポイント

  • encoding/json パッケージにJSONを扱う機能が揃っている
  • MarshalUnmarshal でGoの型とJSON文字列の相互変換ができる

encoding/json パッケージに大体の機能がある

encoding パッケージにはバイト文字列やテキスト文字列をエンコーディングする関数や構造体が定義されています。その中に json パッケージがあり、JSON文字列を扱う関数や構造体が定義されています。

encoding/json

Encoding

Encodingするには json.Marshal を使います。

func Marshal(v interface{}) ([]byte, error)

引数に与えられた値をJSON文字列としてバイトのスライスで返してくれます。

type colorGroup struct {
    ID     int
    Name   string
    Colors []string
}

// Marshal
// go type to json string
group := colorGroup{
    ID:     1,
    Name:   "Reds",
    Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
}
b, err := json.Marshal(group)
if err != nil {
    log.Fatal(err)
}
os.Stdout.Write(b)
// output
// {"ID":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}

基本的な型は Marshalエンコーディングすることができますが、Channel, complex, functionエンコーディング することができません。また、ポインタはエンコーディングできますが、ポインタが指し示す値が対象になるようです。

Decoding

Decodingするには json.Unmarshal を使います。

func Unmarshal(data []byte, v interface{}) error

引数にJSON文字列を渡すことでデコーディングしてくれます。

type colorGroup struct {
    ID     int
    Name   string
    Colors []string
}

// UnMarshal
// json string to go type
groupString := `{
  "id": 1,
  "name": "Orange",
  "colors": ["Red", "Ruby"]
  }`
var unMarshalColor colorGroup
json.Unmarshal([]byte(groupString), &unMarshalColor)
fmt.Println(unMarshalColor)
// output
// {1 Orange [Red Ruby]} 

タグを使ったカスタマイズ

Encoding と Decoding では、構造体にタグを使うことで振る舞いを変更することができます。構造体のタグについては詳しく知らないんですが、構造体のフィールドに追加の属性で文字列を追加することができ、この文字列のことをタグというらしいです。タグはリフレクションで読み取ることが出来るようです。

Go spec struct

タグの使用例としてJSONのフィールド名を変更することができます。タグは json をキーとします。 以下の構造体では ID フィールドにタグを付けてフィールド名を変更しています。

type colorGroup struct {
    ID     int `json:"number"`
    Name   string
    Colors []string
}

colorGroup をEncodingすると、以下のJSONになります。 ID フィールドが number としてEncodingされていますね。

{"number":1,"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}

上記のJSONをDecodingすると、以下の値になります。 numberID フィールドにマッピングされています。

{1 Reds [Crimson Red Ruby Maroon]}

また、タグに omitempty を付けることで、フィールドが特定の値(0やfalse、nilなど)のときにEncodingから省略するように出来ます。例えば以下の構造体では IDomitempty を付けています。

type colorGroup struct {
    ID     int `json:"number"`
    Name   string
    Colors []string
}

これをEncodingすると、 ID が省略されます。

group := colorGroup{
    ID:     0,
    Name:   "Reds",
    Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},
}
// output
// {"Name":"Reds","Colors":["Crimson","Red","Ruby","Maroon"]}

Streaming で Encode と Decode

jsonパッケージにはJSONをStreamingで読み込み・書き込みするための Decoder Encoder 型を定義しています。それぞれ NewDecoder NewEncoderio.Reader io.Writer インターフェイスをラップした値を取得できます。これらの型を使えば、HTTPやWebSocketでJSONをやり取りする事ができるようになります。

使い方はこんな感じになります。標準入力で読み込んだ値を Decode し、キーが Name の値だけを標準出力に向けて Encode します。関数の最初の2行で Decoder Encoder を作成しています。

func main() {
    dec := json.NewDecoder(os.Stdin)
    enc := json.NewEncoder(os.Stdout)
    for {
        var v map[string]interface{}
        if err := dec.Decode(&v); err != nil {
            log.Println(err)
            return
        }
        for k := range v {
            if k != "Name" {
                delete(v, k)
            }
        }
        if err := enc.Encode(&v); err != nil {
            log.Println(err)
        }
    }
}

終わりに

GoでJSONを扱う方法は調べるとまだまだ出てくるのですが、とりあえずの基本としてはこのような内容を抑えておけば良さそうです。標準のパッケージでJSONを扱う事ができるのはかなり便利ですね。そして、何より直感的に書く事ができるので迷う事なく理解できた感じがします。

参考