はじめに
encoding/jsonパッケージを利用してJSONをパースする方法についてまとめます。
下記記事では実際にAPIで取得したJSONをパースしているので、実例を知りたい方はぜひ参考にしてみてください。
【Go言語】net/httpパッケージでAPIリクエストを送信する
encoding/jsonパッケージについて
encoding/jsonはGo言語が提供する標準パッケージです。RFC7159に準拠したJSONのエンコーディングおよびでコーディングを処理するための機能を提供します。Marshal関数やUnmarshal関数を利用することで、JSONからGo言語で扱う値へのマッピングや、Go言語で扱う値からJSONへの変換を実行することができます。
JSONパースの実装
簡単な例
以下のようなJSONをパースするケースを考えます。
{
"name": "Taro",
"age": 25
}
まず、上記JSONに対応するStructを作成します。
type Person struct {
Name string
Age int
}
次に、encoding/jsonパッケージをインポートしてください。エディターによってはパッケージを利用するコードを記述して保存すると自動でインポートされるので、その場合は飛ばしてもOKです。
import (
"encoding/json"
)
jsonパッケージを用いて実際にパース処理を行います。パース処理はUnmarshal関数を用いて行うことができます。Unmarshal関数は以下のように定義されています。
func Unmarshal(data []byte, v interface{}) error
第一引数にパース元のJSONのbyte配列、第二引数に格納先変数のポインタを指定します。ポインタを指定しないとエラーとなり、InvalidUnmarshalErrorが返却されるので注意してください。
それでは、実際にパースしてみましょう。
person := &Person{}
err := json.Unmarshal([]byte(testJson), person)
if err != nil {
log.Fatalln(err)
}
fmt.Printf("Name: %s, Age: %d", person.Name, person.Age)
実行すると以下の結果が出力されます。JSONを期待通りにパースできていることがわかりますね。
Name: Taro, Age: 25
型が異なる場合
もし、JSONに含まれるAgeがstringで返却されてきた場合どうなるでしょうか。Person構造体のAge変数はint型であるため、型が異なります。
{
"name": "Taro",
"age": "25"
}
それでは、実行してみましょう。実行すると以下の結果が返却されたと思います。
つまり、一つでも型が異なるとエラーが返却されるということがわかりました。
2021/08/01 19:25:43 json: cannot unmarshal string into Go struct field Person.Age of type int
Process exiting with code: 1 signal: false
JSONに含まれている項目が構造体に含まれていない場合
JSONに含まれている項目が構造体に含まれていない場合にどうなるか検証してみましょう。
JSONに”email”の項目を追加しました。
{
"name": "Taro",
"age": 25,
"email": "xxxx@test.com"
}
Person構造体はそのままです。そのため、”email”が構造体には足りていない状況となります。
type Person struct {
Name string
Age int
}
この状態でパース処理を実行してみます。すると、以下の結果が返却されました。
エラーとはならずに、無事にパースできていますね。つまり、JSONに含まれている項目が構造体に含まれていない場合、含まれていない項目は無視され、問題なくパースできるということになります。
Name: Taro, Age: 25
JSONに含まれていない項目を構造体が持っている場合
今度は上記と反対に、JSONに含まれていない項目を構造体が持っている場合を検証してみます。
JSONを元に戻し、emailの項目をPerson構造体に追加します。
{
"name": "Taro",
"age": 25
}
type Person struct {
Name string
Age int
Email string
}
Emailの中身も確認できるようみfmt.Printlnを一部修正します。
fmt.Printf("Name: %s, Age: %d, Email: %s", person.Name, person.Age, person.Email)
それでは、パース処理を実行してみましょう。実行すると以下の結果が返却されました。
つまり、JSONに含まれていない項目を構造体が持っている場合、パース処理は成功し、その項目には型のデフォルト値が格納されます。
Name: Taro, Age: 25, Email:
配列の場合
配列の場合も、格納する変数を配列にすることで同様にパースすることができます。
testJson := `[
{
"name": "Taro",
"age": 25
},
{
"name": "Jiro",
"age": 20
}
]`
persons := []Person{}
err := json.Unmarshal([]byte(testJson), &persons)
if err != nil {
log.Fatalln(err)
}
fmt.Printf("%v", persons)
[{Taro 25 } {Jiro 20 }]
ネスト構造の場合
ネスト構造を持ったJSONをパースするケースを考えてみます。以下のように”account”がネストされた構造となっています。
{
"name": "Taro",
"age": 25,
"account":
{
"facebook": "XXXXXXXXXXXX",
"twitter": "YYYYYYYYYYYY"
}
}
このような場合、ネスト構造に対応するようにPerson構造体を修正します。具体的には、JSONの”account”に対応するAccount構造体を作成し、それをPerson構造体の変数とします。そうすることでJSONとPerson構造体が同じ構造となります。
type Person struct {
Name string
Age int
Account Account
}
type Account struct {
Facebook string
Twitter string
}
JSONと構造体が対応していれば、あとは今までと同じ手順でパースできます。
testJson := `{
"name": "Taro",
"age": 25,
"account":
{
"facebook": "XXXXXXXXXX",
"twitter": "YYYYYYYYYY"
}
}`
person := Person{}
err := json.Unmarshal([]byte(testJson), &person)
if err != nil {
log.Fatalln(err)
}
fmt.Printf("Name: %s, Age: %d, Account.Facebook: %s, Account.Twitter: %s", person.Name, person.Age, person.Account.Facebook, person.Account.Twitter)
Name: Taro, Age: 25, Account.Facebook: XXXXXXXXXX, Account.Twitter: YYYYYYYYYY
異なる変数名にマッピングする
JSONの”name”を”first_name”に項目名を変更しました。このfirst_name項目をPerson構造体のNameに格納するようにマッピングしたいとします。
{
"first_name": "Taro",
"age": 25,
"account":
{
"facebook": "XXXXXXXXXXXX",
"twitter": "YYYYYYYYYYYY"
}
}
項目名が異なる場合、今まで通りのままUnmarshal関数でパースすると、項目名が異なる変数にはマッピングされず、デフォルト値が格納されます。
Name: , Age: 25, Account.Facebook: XXXXXXXXXX, Account.Twitter: YYYYYYYYYY
異なる変数名にマッピングする場合、タグを使います。構造体の変数に`json:"<JSON項目名>,<オプション>"`
のタグをつけることで、タグで指定した項目名を任意の変数にマッピングすることができます。
type Person struct {
Name string `json:"first_name"`
Age int `json:"age"`
Account Account `json:"account"`
}
type Account struct {
Facebook string `json:"facebook"`
Twitter string `json:"twitter"`
}
Person.Name変数に`json:"first_name"`
とタグをつけたため、JSONの”first_name”項目とPerson.Name変数がマッピングされます。
Name: Taro, Age: 25, Account.Facebook: XXXXXXXXXX, Account.Twitter: YYYYYYYYYY
タグのオプション
タグには「string」「omitempty」「-」の3つのオプションがあります。それぞれ以下の意味合いを持ちます。
オプション | 意味 |
---|---|
string | 文字列を受け取って変数の型に変換する |
omitempty | 空の値(false、0、nilポインター、nilインターフェイス、空配列、空スライス、空マップ、空文字列)の場合、そのフィールドをエンコードから除外する |
– | 常に省略される |
Ageに”string”オプション、Facebookに”omitempty”オプション、Twitterに”-“オプションをつけました。
以下のJSONをパースしてみます。
type Person struct {
Name string `json:"first_name"`
Age int `json:"age,string"`
Account Account `json:"account"`
}
type Account struct {
Facebook string `json:"facebook,omitempty"`
Twitter string `json:"-"`
}
{
"first_name": "Taro",
"age": "25",
"account":
{
"facebook": null,
"twitter": "YYYYYYYYYYYY"
}
}
デバッグしてperson構造体の中身を確認しました。Ageが25の数値に変換されていること、FacebookとTwitterにデフォルト値が格納されていることわかります。
また、”omitempty”および”-“オプションで省略される場合、デフォルト値が格納されることがわかりました。
まとめ
JSONパース方法のまとめは以上となります。細かい挙動を知っていないと予期しないバグに繋がるので、様々なパターンを検証できて良かったです。Marshal関数の使い方についてはまた別記事でまとめようと思います。
ありがとうございました。
コメント