Goでネスト構造を持つ&配列型のJSONファイルを読み込む方法

読み込むJSONデータ

Twitter APIからのレスポンスを模したJSONをGoで読み込んでみます。
(実際のTwitter APIからのレスポンスにはもっと多くのデータが含まれていますが、ここでは簡略化しています)

timeline.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
  [
    {
      "created_at": "Wed Dec 16 13:55:51 +0000 2020",
      "id": 1145148108931919334,
      "full_text": "\u798F\u5CA1\u770C\u6C11\u304B\u3089\u306E\u304A\u77E5\u3089\u305B\u3067\u3059\u3002\u798F\u5CA1\u770C\u3067\u306F\u73FE\u5728\u3001\u6DF1\u591C\u306E\u98EF\u30C6\u30ED\u64B2\u6EC5\u904B\u52D5\u3092\u5B9F\u65BD\u3057\u3066\u3044\u307E\u3059\u3002\u6DF1\u591C\u306E\u98EF\u30C6\u30ED\u306F\u3057\u306A\u3044\u3001\u3055\u305B\u306A\u3044\u3001\u8A31\u3055\u306A\u3044\u3002\u798F\u5CA1\u770C\u304B\u3089\u3001\u6DF1\u591C\u306E\u98EF\u30C6\u30ED\u3092\u306A\u304F\u3057\u307E\u3057\u3087\u3046",
      "user": {
        "id": 123456789,
        "name": "\u671d\u9727",
        "screen_name": "asagiri96mc"
      }
    },
    {
      "created_at": "Wed Dec 16 13:48:39 +0000 2020",
      "id": 1234567890987654321,
      "full_text": "\u6765\u5E74\u306EC99\u7533\u3057\u8FBC\u307F\u307E\u305B\u3093\u3067\u3057\u305F\uFF01\u300E\u30A6\u30FC\u30D1\u30FC\u30EB\u30FC\u30D1\u30FC\u3067\u3082\u308F\u304B\u308B\u0047\u006F\u8A00\u8A9E\u300F\u3092\u9812\u5E03\u3044\u305F\u3057\u307E\u305B\u3093\u3002\u5834\u6240\u306F\u81EA\u5B85\u3067\u3059\u3002\u3088\u308D\u3057\u304F\u304A\u9858\u3044\u3057\u307E\u3059\u3002",
      "extended_entities": {
        "media": [
          {
            "id": 1234567898765432101,
            "media_url_https": "https://pbs.twimg.com/media/EpC3IbJVQAEDpMm.jpg"
          }
        ]
      },
      "user": {
        "id": 987654321,
        "name": "orangeliner.net",
        "screen_name": "orangeliner_"
      }
    },
    {
      "created_at": "Wed Dec 16 11:49:00 +0000 2020",
      "id": 1234567890123456789,
      "full_text": "\u306D\u3053\u3082\u3075\u3082\u3075\uFF5E",
      "extended_entities": {
        "media": [
          {
            "id": 1122334455667788990,
            "media_url_https": "https://pbs.twimg.com/media/EpSQAe2U8AYLBsA.jpg"
          },
          {
            "id": 1122334455667788999,
            "media_url_https": "https://pbs.twimg.com/media/EpNKZOXVoAAMczK.jpg"
          }
        ]
      },
      "user": {
        "id": 123456789,
        "name": "\u671d\u9727",
        "screen_name": "asagiri96mc"
      }
    }
  ]

Goで読み込む

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
)

// [1] 構造体のフィールドとJSONのキーを紐付け
type Tweet struct {
	Date     string `json:"created_at"`
	ID       int    `json:"id"`
	Text     string `json:"full_text"`
	Entities Entity `json:"extended_entities"`
	User     User   `json:"user"`
}

// [2] JSONと同様のネスト構造で構造体を定義
type Entity struct {
	Media []*Media `json:"media"`
}

type Media struct {
	ID  int    `json:"id"`
	URL string `json:"media_url_https"`
}

type User struct {
	// 使わないJSONキーは取り込まなくても問題ない
	// ID         int    `json:"id"`
	Name       string `json:"name"`
	ScreenName string `json:"screen_name"`
}

func main() {
	file, err := ioutil.ReadFile("timeline.json")
	if err != nil {
		// エラー処理
	}

	// [3] 配列型のJSONデータを読み込む
	timeline := make([]*Tweet, 0)
	err = json.Unmarshal(file, &timeline)
	if err != nil {
		// エラー処理
	}

	// スライスの要素数を取得
	fmt.Printf("全 %d 件\n\n", len(timeline))

	// スライスの要素を順に取り出す
	for _, tweet := range timeline {

		// [2] ネストした構造体のフィールドを呼び出す
		fmt.Printf("%s (@%s)\n", tweet.User.Name, tweet.User.ScreenName)
		fmt.Println(tweet.Text)

		// .Entities.Media が存在しない要素もあるので、0個でないことを確認
		if len(tweet.Entities.Media) != 0 {
			for i, media := range tweet.Entities.Media {
				fmt.Printf("画像URL[%d]: %s\n", i, media.URL)
			}
		}
		fmt.Printf("ID: %d  Posted at: %s\n\n", tweet.ID, tweet.Date)
	}
}

実行結果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ go run load_json.go

全 3 件

朝霧 (@asagiri96mc)
福岡県民からのお知らせです。福岡県では現在、深夜の飯テロ撲滅運動を実施しています。深夜の飯テロはしない、させない、許さない。福岡県から、深夜の飯テロをなくしましょう
ID: 1145148108931919334  Posted at: Wed Dec 16 13:55:51 +0000 2020

orangeliner.net (@orangeliner_)
来年のC99申し込みませんでした!『ウーパールーパーでもわかるGo言語』を頒布いたしません。場所は自宅です。よろしくお願いします。
画像URL[0]: https://pbs.twimg.com/media/EpC3IbJVQAEDpMm.jpg
ID: 1234567890987654321  Posted at: Wed Dec 16 13:48:39 +0000 2020

朝霧 (@asagiri96mc)
ねこもふもふ~
画像URL[0]: https://pbs.twimg.com/media/EpSQAe2U8AYLBsA.jpg
画像URL[1]: https://pbs.twimg.com/media/EpNKZOXVoAAMczK.jpg
ID: 1234567890123456789  Posted at: Wed Dec 16 11:49:00 +0000 2020

[1] 構造体のフィールドとJSONのキーを紐付ける

  • JSON
    1
    2
    3
    4
    5
    6
    
    {
      "created_at": "Wed Dec 16 13:48:39 +0000 2020",
      "id": 1234567890987654321,
      "full_text": "...",
      ...
    }
    
  • Go
    1
    2
    3
    4
    5
    6
    
    type Tweet struct {
    	Date     string `json:"created_at"`
    	ID       int    `json:"id"`
    	Text     string `json:"full_text"`
    	...
    }
    

構造体の定義で `json:"key_name"` のように記述して、JSONのキーを紐付ける。
json.Unmarshal() を実行時に、JSONの "key_name" キーの値が構造体のフィールド(変数)に格納される。

[2] ネストしたJSONを読み込む構造体の定義

  • JSON
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    {
      ...
      "extended_entities": {
        "media": [
          {
            "id": 1234567898765432101,
            "media_url_https": "https://pbs.twimg.com/media/EpC3IbJVQAEDpMm.jpg"
          },
          ...
        ]
      },
      "user": {
        "id": 987654321,
        "name": "orangeliner.net",
        "screen_name": "orangeliner_"
      },
      ...
    }
    
  • Go
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    type Tweet struct {
    	...
    	Entities Entity `json:"extended_entities"`
    	User     User   `json:"user"`
    	...
    }
    
    type Entity struct {
    	Media []*Media `json:"media"`
    }
    
    type Media struct {
    	ID  int    `json:"id"`
    	URL string `json:"media_url_https"`
    }
    
    type User struct {
    	Name       string `json:"name"`
    	ScreenName string `json:"screen_name"`
    }
    

JSONのネスト構造に合わせて、構造体もネスト構造で定義する。
この例では、「Tweet型に含まれるフィールド Entities は Entity型、Entity型に含まれるフィールド Media は Media型を含むスライス、Media型に含まれるフィールド ID は…」といった形で入れ子構造になっている。

  • Go
    1
    2
    3
    4
    5
    6
    
    // スライス timeline にデータが格納されているとする
    
    timeline[0].Entities.Media[0].ID
    // => 1234567898765432101
    timeline[0].User.ScreenName
    // => "orangeliner_"
    

ネストした構造体のフィールドは、フィールド名を .(ドット)で繋げることで取得できる。
スライスが含まれる場合は [i] でインデックスを指定する。

[3] 配列型のJSONデータを読み込む

  • Go
    1
    2
    
    timeline := make([]*Tweet, 0)
    err = json.Unmarshal(file, &timeline)
    

データを格納する型として、構造体ではなく、要素数 0 のスライスを作成する。
JSONデータ内の配列の要素をこのスライスに格納する。