30
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

UL Systems (ウルシステムズ)Advent Calendar 2017

Day 19

go言語でJSONと戯れる

Last updated at Posted at 2017-12-18

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

30
20
0

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
  3. You can use dark theme
What you can do with signing up
30
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?