Edited at

go言語でJSONと戯れる

More than 1 year has passed since last update.

本エントリは、Qiita Advent Calendar 2017 ulgeekの19日目です。


はじめに

現在業務でJSONを受け取る帳票システムを開発しています。

そのシステムはJavaで実装予定なのですが、個人的に興味のあったgo言語で、

どのようにJSONを扱うのか、ふと気になりました。

JSONでデータのやり取りを行うシステムが多い中、

今後go言語で開発するという素敵な機会があるかもしれないと思い、

(今はなくても、私が色々知っていれば、周りの人を洗脳できるかもしれないと期待しつつ...)

今回はgo言語とJSONについて、書いてみようと思います。


何をやるか

go言語の標準パッケージであるencoding/jsonで提供されている、

Marshal関数、Unmarshal関数を用いて、ひたすらJSON文字列を操作します。


標準パッケージでJSONを扱う

go言語でJSONを扱うには、encoding/jsonという標準パッケージを利用します。

以下のように、JSON文字列を用意します(HIDEとTAIJIは忘れていませんよ...ただ文字列が長くなってしまうので...)。この文字列を操作します。

var xjapanJson string = `

{
"members": [
{ "name": "Toshl", "instrument": "vocal" },
{ "name": "PATA", "instrument": "guitar" },
{ "name": "HEATH", "instrument": "bass" },
{ "name": "SUGIZO", "instrument": "guitar" },
{ "name": "YOSHIKI", "instrument": "drums" }
],
"songs": ["紅", "Silent Jealousy"]
}
`

encoding/jsonパッケージには、JSON文字列を簡単に扱うために、2つの関数が用意されています。


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

json.Unmarshal関数は、構造体にjsonタグがあれば、対応する変数に値を代入します。

第1引数にJSONのバイト列、第2引数にJSONをマッピングしたい値(今回の場合、構造体)のポインターです。

今回の場合、new()で生成したXJapan構造体のポインターを渡しています。

package main

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

type Musician struct {
Name string `json:"name"`
Instrument string `json:"instrument"`
}

type XJapan struct {
Members []Musician `json:"members"`
Songs []string `json:"songs"`
}

func main() {
// Unmarshal結果の格納先である構造体のポインターを取得
xJapan := new(XJapan)

// JSON文字列をバイト列にキャスト
jsonBytes := []byte(xjapanJson)

// xJapanにバイト列を格納する
if err := json.Unmarshal(jsonBytes, xJapan); err != nil {
fmt.Println(err)
return
}
for _, members := range xJapan.Members {
fmt.Printf("NAME: %-7s INSTRUMENT: %s\n", members.Name, members.Instrument)
}
/*
出力結果
NAME: Toshl INSTRUMENT: vocal
NAME: PATA INSTRUMENT: guitar
NAME: HEATH INSTRUMENT: bass
NAME: SUGIZO INSTRUMENT: guitar
NAME: YOSHIKI INSTRUMENT: drums
*/

for _, song := range xJapan.Songs {
fmt.Println(song)
}
/*
出力結果

Silent Jealousy
*/

}


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

json.Marshal関数は、構造体のフィールド名をJSONのキー名として、JSON文字列を生成します。

package main

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

type Musician struct {
Name string `json:"name"`
Instrument string `json:"instrument"`
}

type XJapan struct {
Members []Musician `json:"members"`
Songs []string `json:"songs"`
}

func main() {
// XJapan構造体のポインターを取得
xJapan := new(XJapan)

// JSON文字列をバイト列にキャスト
jsonBytes := []byte(xjapanJson)

// xJapanにバイト列を格納する
if err := json.Unmarshal(jsonBytes, xJapan); err != nil {
fmt.Println(err)
return
}

// 構造体をJSON文字列に変換
xJapanJson, err := json.Marshal(xJapan)
if err != nil {
fmt.Println(err)
return
}
out := new(bytes.Buffer)
// スペース4つを追加しJSONを整形
json.Indent(out, xJapanJson, "", " ")
fmt.Println(out.String())
/*
出力結果
{
"members": [
{
"name": "Toshl",
"instrument": "vocal"
},
{
"name": "PATA",
"instrument": "guitar"
},
{
"name": "HEATH",
"instrument": "bass"
},
{
"name": "SUGIZO",
"instrument": "guitar"
},
{
"name": "YOSHIKI",
"instrument": "drums"
}
],
"songs": [
"紅",
"Silent Jealousy"
]
}
*/

}

とても簡単ですね。


タグ

しれっと出てきましたが、構造体のフィールドについている、json:"〇〇〇"は何かと思いますよね。

go言語の構造体には、タグと呼ばれるメタデータを付与できます。このタグ情報は、reflectを用いることで実行時に参照できます。

encoding/jsonパッケージでも内部でreflectを使い、構造体の変数とJSONのキーを紐づけしています。

// タグをつける

Name string `json:"name"`

// JSONタグを付与しなくても、JSONとのマッピングは行われる
Name string

// フィールドが小文字でもOK
name string

// JSONタグを付与すれば、JSONのキー名と全く違う名前のフィールドを定義できる
ABC string `json:"name"`

ドキュメントを確認すると、大文字小文字は区別しないと書いてありました。


To unmarshal JSON into a struct,Unmarshal matches incoming object keys to the keys used by Marshal (either the struct field name or its tag),preferring an exact match but also accepting a case-insensitive match.

Unmarshal will only set exported fields of the struct.


ただし、最後の一文によると、別パッケージで定義された構造体に値をセットしたい場合、エクスポートされたフィールドにのみセットされるので注意が必要です。

go言語の仕様上、先頭の文字が大文字である変数・関数のみがパッケージ外でも使用できます。

タグには、以下のようにオプションを付与できます。

// `json:"-"`

// ハイフンを指定すると、このフィールドは無視される
Name string `json:"-"`

// `json:",omitempty"`
// "omitempty"を指定すると、JSONに"name"キーの値が空の場合、このフィールドを削除する
// 空の状態とは、false, 0, nilポインター、nilインターフェース、要素のないarray・slice・map・stringとのこと
Name string `json:"name,omitempty"`

// `json:",string"`
// 強制的に文字列で出力する
Int64String int64 `json:",string"`


階層構造を持つJSONの扱い

先ほどまでは、以下のように、個別に構造体を定義していました。

// 階層ごとに構造体を定義

type Musician struct {
Name string `json:"name"`
Instrument string `json:"instrument"`
}

type XJapan struct {
Members []Musician `json:"members"`
Songs []string `json:"songs"`
}

以下のように、JSONの階層構造を、構造体の入れ子構造で表すことができます。

// 構造体を入れ子にできる

// 個別の構造体を定義した場合と同じ意味!
type XJapan struct {
Members []struct {
Name string `json:"name"`
Instrument string `json:"instrument"`
} `json:"members"`
Songs []string `json:"songs"`
}

シンプルですね。

より複雑なJSONを扱う場合は、以下の投稿が参考になりました。

Go言語でJSONに泣かないためのコーディングパターン


おわりに

go言語でのJSONの操作方法を見てきました。

標準パッケージでかなりのことまで簡単にできることがわかりました。

まだまだ知らないテクニックがありそうですが、今回はここまでです。

周囲の人をgo言語色に洗脳するために、少しずつでも勉強していきたいです。