本エントリは、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言語色に洗脳するために、少しずつでも勉強していきたいです。