JSON の操作
某所であった JSON データの操作に関するやり取りが面白かったのでネタにしてみる。
まず,サンプルの JSON データとして以下のデータを使う。
var sampleJSON = `{
"books": [
{
"Type": "aozora",
"ID": "56839",
"Title": "ニャルラトホテプ",
"OriginalTitle": "NYARLATHOTEP",
"URL": "https://www.aozora.gr.jp/cards/001699/card56839.html",
"ProductType": "青空文庫",
"Creators": [
{
"Name": "ラヴクラフト ハワード・フィリップス"
},
{
"Name": "大久保 ゆう",
"Role": "翻訳"
}
],
"PublicationDate": "2014-04-04",
"LastRelease": "2015-08-19"
},
{
"Type": "aozora",
"ID": "4307",
"Title": "グリゴリの捕縛",
"URL": "https://www.aozora.gr.jp/cards/000021/card4307.html",
"ProductType": "青空文庫",
"Creators": [
{
"Name": "白田 秀彰"
}
],
"PublicationDate": "2001-11-26",
"LastRelease": "2014-09-17"
}
]
}`
このデータを簡単に扱う方法を考える。
とりあえず map にデコードする
お手軽なのは JSON オブジェクトを map[string]interface
に,配列を []interface
にデコードすることである。
たとえばこんな感じ。
func main() {
var decodeData map[string]interface{}
if err := json.Unmarshal([]byte(sampleJSON), &decodeData); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
book0 := decodeData["books"].([]interface{})[0]
creators := book0.(map[string]interface{})["Creators"].([]interface{})
fmt.Println(creators)
// Output:
// [map[Name:ラヴクラフト ハワード・フィリップス] map[Name:大久保 ゆう Role:翻訳]]
}
ただ,このやり方では map や interface にデコードしたデータを取り出す際にキャストの嵐になるので,どうしても記述が煩雑になる。
koron/go-dproxy パッケージを使う
上述のデコードしたデータをもう少し簡単に扱うために koron/go-dproxy パッケージを利用する。 こんな感じ。
func main() {
var decodeData map[string]interface{}
if err := json.Unmarshal([]byte(sampleJSON), &decodeData); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
a, err := dproxy.New(decodeData).M("books").A(0).M("Creators").Array()
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
fmt.Println(a)
// Output:
// [map[Name:ラヴクラフト ハワード・フィリップス] map[Name:大久保 ゆう Role:翻訳]]
}
エラーハンドリングも含めてかなりスッキリ書ける。 更に koron/go-dproxy パッケージは JSON Pointer (RFC 6901) にも対応しているそうで
func main() {
var decodeData map[string]interface{}
if err := json.Unmarshal([]byte(sampleJSON), &decodeData); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
a, err := dproxy.New(decodeData).P("/books/0/Creators").Array()
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
fmt.Println(a)
// Output:
// [map[Name:ラヴクラフト ハワード・フィリップス] map[Name:大久保 ゆう Role:翻訳]]
}
といった書き方もできる。 便利!
JSON デコード用構造体を生成するツールを利用する
利用する JSON データの構成があらかじめ分かっているのなら yudppp/json2struct を使って JSON データから構造体を生成することができる。
yudppp/json2struct はコマンドラインツールとして利用する。 こんな感じ。
$ cat sample.json | json2struct -name=Booksdata
type Booksdata struct {
Books []BooksdataBook `json:"books"`
}
type BooksdataBook struct {
Creators []BooksdataBookCreator `json:"Creators"`
ID string `json:"ID"`
LastRelease string `json:"LastRelease"`
OriginalTitle string `json:"OriginalTitle"`
ProductType string `json:"ProductType"`
PublicationDate string `json:"PublicationDate"`
Title string `json:"Title"`
Type string `json:"Type"`
URL string `json:"URL"`
}
type BooksdataBookCreator struct {
Name string `json:"Name"`
Role string `json:"Role"`
}
このコードを組み込んで使えばよい。 こんな感じ。
func main() {
var decodeData Booksdata
if err := json.Unmarshal([]byte(sampleJSON), &decodeData); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
fmt.Println(decodeData.Books[0].Creators)
// Output:
// [{ラヴクラフト ハワード・フィリップス } {大久保 ゆう 翻訳}]
}
jq ぽい操作が欲しい
もっとラフに jq ぽく JSON データを扱えないか。 たとえば savaki/jq を使えばできる。
func main() {
op, _ := jq.Parse(".books.[0].Creators")
v, _ := op.Apply([]byte(sampleJSON))
fmt.Println(string(v))
// Output:
// [{"Name":"ラヴクラフト ハワード・フィリップス"},{"Name":"大久保 ゆう","Role":"翻訳"}]
}
savaki/jq は JSON データのフィルタとして機能する。 操作としては savaki/jq で必要な部分を抽出した後,これまで述べた方法で操作する,といった感じに組み合わせて使うといいかもしれない。
ブックマーク
参考図書
- プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)
- Alan A.A. Donovan (著), Brian W. Kernighan (著), 柴田 芳樹 (翻訳)
- 丸善出版 2016-06-20
- 単行本(ソフトカバー)
- 4621300253 (ASIN), 9784621300251 (EAN), 4621300253 (ISBN), 9784621300251 (ISBN)
- 評価
著者のひとりは(あの「バイブル」とも呼ばれる)通称 “K&R” の K のほうである。この本は Go 言語の教科書と言ってもいいだろう。