【Go言語】encoding/jsonパッケージでJSONをパースする

  • URLをコピーしました!
目次

はじめに

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”および”-“オプションで省略される場合、デフォルト値が格納されることがわかりました。

debug画像

まとめ

JSONパース方法のまとめは以上となります。細かい挙動を知っていないと予期しないバグに繋がるので、様々なパターンを検証できて良かったです。Marshal関数の使い方についてはまた別記事でまとめようと思います。
ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次