orangeliner_net BLOG
旅とサブカル、時々ガジェット。
https://blog.orangeliner.net/
2021-01-19T00:00:00+09:00
朝霧
[GCP] Cloud Functions が Ruby に対応! ローカル環境でサクっと開発
https://blog.orangeliner.net/tech/cloud-functions-ruby.html
2021-01-19T00:00:00+09:00
朝霧
GCP の FaaS (Functions as a Service)、Cloud Functions がついに Ruby に対応しました!!
AWS Lambda では2018年末の時点で既に Ruby が使えていたので、Cloud Functions にも来ないかな~と思っていた人には朗報ですね。
現在はβ版で、Ruby 2.6 および Ruby 2.7 のバージョンをサポートしています。
Cloud Functions の公式ドキュメントにも既に Ruby 用のサンプルコードが追加されており、普段 Ruby を書いてる人であればすぐに使えると思います。
Ruby comes to Cloud Functions - Google Cloud Blog
Ruby クイックスタート - Google Cloud Functions に関するドキュメント
Cloud Functions の Ruby サポートと併せて、ローカル環境での Ruby を使った Cloud Functions & Cloud Run 開発をサポートする Functions Framework for Ruby (α版)も公開されました。
GitHub - GoogleCloudPlatform/functions-framework-ruby: FaaS (Function as a service) framework for writing portable Ruby functions
Functions Framework for Ruby のドキュメント
作成した関数(スクリプト)のデプロイ方法は他の言語とほぼ変わらないと思うので省略しますが、今回は Functions Framework for Ruby(以下、Functions Framework)を使って、ローカル環境でどれほどスムーズに開発ができるのか試してみました。
gemのインストール
HTTPトリガー関数の作成
ローカル環境での実行
テスティングフレームワークとの連携
まとめ
参考
gemのインストール
Ruby と Cloud Functions の仲介をする Functions Framework は gem として提供されています。
1
$ gem install functions_framework
HTTPトリガー関数の作成
ここでは Cloud Functions の最も一般的な使い方と思われる、ブラウザ等からの HTTP アクセスによって起動する HTTPトリガー関数 を作成して動かしてみます。
HTTP トリガー - Google Cloud Functions に関するドキュメント
もちろん、HTTP アクセス以外に、Pub/Sub メッセージの受信や Cloud Storage へのファイル保存をトリガーにすることも可能です。
Google Cloud Pub/Sub トリガー - Google Cloud Functions に関するドキュメント
Google Cloud Storage トリガー - Google Cloud Functions に関するドキュメント
ソースコード内で functions_framework ライブラリを読み込み、 FunctionsFramework.http ~ end ブロック内に、HTTP アクセスされた時の処理を定義します。
1
2
3
4
5
6
7
8
9
#== app.rb ==#
require "functions_framework"
# 関数名: hello_http
FunctionsFramework.http "hello_http" do |req|
name = req.params["name"].to_s
"Hello, #{name.empty? ? "World" : name}!"
end
HTTP アクセスによって関数が実行されると、 Rack::Request オブジェクトがブロック引数に渡されます(ここでは req )。
GET/POST パラメータもこの Rack::Request オブジェクト内に含まれていますので、params メソッド等で取得します。
ここでは GET パラメータから name の値を取得し、 "Hello, #{name}!"(値が無ければ "Hello, World!")を出力します。
ローカル環境での実行
functions-framework-ruby コマンドで、ローカル環境で関数を実行し動作を確認できます。
--source にファイル名(デフォルトは app.rb )、 --target に関数名を指定します。
gem を bundle install した場合は、 bundle exec を頭に付けてコマンドを実行します。
1
2
3
$ functions-framework-ruby --source=app.rb --target=hello_http
または
$ bundle exec functions-framework-ruby --source=app.rb --target=hello_http
コマンドを実行すると Web サーバ(Puma)が起動するので、ブラウザや curl コマンドでアクセスすると、関数が実行され Rack::Request オブジェクトが渡されます。
ブラウザで http://localhost:8080 にアクセスするとレスポンスが確認できました。
GET パラメータなし
GET パラメータあり (name=Ruby)
Web サーバは Ctrl+C で終了します。
テスティングフレームワークとの連携
Minitest や RSpec といったテスティングフレームワークと Functions Framework を連携させて、ローカル環境で作成した関数のテストを行うこともできます。
テストコード内で functions_framework/testing ライブラリを使用します。
load_temporary
ソースファイルの読み込み
make_get_request
GET リクエスト
call_http
関数の呼び出し(関数名を指定)
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
#== app_test.rb ==#
require 'minitest/autorun'
require 'functions_framework/testing'
class MyTest < Minitest::Test
include FunctionsFramework::Testing
# GETパラメータなし
def test_get_no_param
# ソースファイル読み込み
load_temporary './app.rb' do
request = make_get_request("http://localhost:8080/")
# 関数名を指定
response = call_http("hello_http", request)
assert_equal(200, response.status)
assert_equal("Hello, World!", response.body.join)
end
end
# GETパラメータあり (name=Ruby)
def test_get_with_param
load_temporary './app.rb' do
request = make_get_request("http://localhost:8080/?name=Ruby")
response = call_http("hello_http", request)
assert_equal(200, response.status)
assert_equal("Hello, Ruby!", response.body.join)
end
end
end
前項(ローカル環境での実行)の手順で Web サーバを起動し、別のターミナルから Ruby のテストコードを実行します。
1
2
3
4
5
6
7
8
9
$ ruby app_test.rb
Run options: --seed 34828
# Running:
..
Finished in 0.023939s, 83.5457 runs/s, 167.0914 assertions/s.
2 runs, 4 assertions, 0 failures, 0 errors, 0 skips
無事、テストが成功しました。
まとめ
ちょっとしたスクリプトやバッチをサーバーレスで実行できて便利な Cloud Functions ですが、普段 Ruby を書いてる人にはさらに手軽に使える選択肢になったと思います。
ローカル環境での実行・テストも gem を導入すれば複雑な設定も不要で、サクッと開発できると感じました。
Cloud Functions には実行メモリやタイムアウト(処理時間)の制約があるため、大幅なパフォーマンスの向上を実現した Ruby 3系にも対応すれば、さらに活用の幅が広がるのではと思いました。
今後のアップデートに期待です。
参考
Google Cloud FunctionsがRubyのサポートを発表。ローカル環境での開発やテストを可能にするフレームワークも - Publickey
[GCP] Cloud Functions でレベル付きログを出力する一番簡単な方法
https://blog.orangeliner.net/tech/cloud-functions-easy-logging.html
2021-01-13T00:00:00+09:00
朝霧
GCPで提供されているFaaS (Functions as a Service)、Cloud Functions ではちょっとしたスクリプトやバッチ(「関数」と呼ばれる)をクラウド上で実行でき、十分な無料枠も準備されているので、個人ユースでもとても便利なサービスです。
Cloud Functions で関数を実行した結果を確認するために、GCPのログ管理サービス Cloud Logging へログを出力できます。
CLoud Logging に保管されたログは GCP のスマホアプリで確認したりもできますが、全てのログを表示すると確認が不要なログも多く表示されてしまい、必要な情報が埋もれてしまいます。
そこで、構造化ログ として出力することで、ログ毎にレベルを設定し、絞り込みをして必要なログのみを表示させることが可能です。
ログの作成、表示、処理 - Google Cloud Functions に関するドキュメント
しかし、GCPの公式ドキュメントを読むと Cloud Logging クライアント ライブラリ とか Logging API とか書いてあってよく分からなくなった(あとで使い分けを理解した)ので、サクッと使えるように一番簡単な方法をまとめてみました。
※ Cloud Functions だけでなく、Cloud Run でも同じ方法が使えるようです。
結論
JSONの出力方法
ログレベルとして指定可能な値
言語別サンプルコード
Python 3
Go
Node.js
まとめ
参考
結論
標準出力にJSONで出力する
1
{"severity":"INFO","message":"This is info message."}
"severity" の値にログレベル、"message" の値にログテキストを指定する。
GCP側で "severity" の値を読み取って、ログにレベルを設定してくれる。
JSONの出力方法
各言語の標準出力へ文字列を出力するメソッドを使用すればOK。
Python
print()
Go
fmt.Printf / fmt.Println 等
Node.js
console.log()
console.info() console.warn() console.error() といったメソッドもあるが、ログレベルの違いとしては認識されない…?
ログレベルとして指定可能な値
DEFAULT (レベル設定なし)
DEBUG (重大度:低)
INFO
NOTICE
WARNING
ERROR
CRITICAL
ALERT
EMERGENCY (重大度:高)
通常使用するレベルとしては DEBUG、INFO、WARNING、ERROR あたりでしょうか。
Cloud Functions では、重大度の最も低い DEBUG レベルで、関数の作成、設定変更、呼び出し、開始、終了…等の細かいログが自動的に出力されます。
任意の処理結果の情報(日付と処理件数…等)をログ出力しておき後から確認したい場合は INFO レベルで出力しておき、ログ確認時に INFO レベル以上 を絞り込み表示することで、確認不要な DEBUG レベルのログが表示されずスマートです。
言語別サンプルコード
各言語 (Python 3/Go/Node.js) で INFO レベルのログを出力するサンプルコード
2行目は、日付(文字列)と処理件数(整数値)をログに出力する想定です。
1
2
{"severity":"INFO","message":"This is info message."}
{"severity":"INFO","message":"Date: 2021/01/13 / Count: 123"}
Python 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import json
def logJSON(logLevel, message):
return json.dumps({
"serverity": logLevel,
"message": message,
})
print(logJSON("INFO", "This is info message."))
date = "2021/01/13"
count = 123
print(logJSON("INFO", f"Date: {date} / Count: {count}"))
Go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import (
"encoding/json"
"fmt"
)
func logJSON(logLevel, message string) string {
entry := map[string]string{
"severity": logLevel,
"message": message,
}
bytes, _ := json.Marshal(entry)
return string(bytes)
}
func main() {
fmt.Println(logJSON("INFO", "This is info message."))
date := "2021/01/13"
count := 123
fmt.Println(logJSON("INFO", fmt.Sprintf("Date: %s / Count: %d", date, count)))
}
Node.js
1
2
3
4
5
6
7
8
9
10
11
12
function logJSON(logLevel, message) {
return JSON.stringify({
severity: logLevel,
message: message,
});
}
console.log(logJSON("INFO", "This is info message."));
let date = "2021/01/13"
let count = 123
console.log(logJSON("INFO", `Date: ${date} / Count: ${count}`));
まとめ
Cloud Functions からレベル付きログを出力するだけであれば、設定したいレベルとテキストをJSONで標準出力するだけでOKでした。
個人的なちょっとしたスクリプトやバッチ程度であればサクッと適用できますね。
Cloud Logging クライアント ライブラリ(言語別ライブラリ)を使えば Cloud Functions では対応していない PHP や Ruby といった言語のスクリプトから、Logging API を使えば Cloud Functions 以外のプラットフォームからも GCP の Cloud Logging へログを送れるようなので、機会があれば試してみようと思います。
参考
ログの作成、表示、処理 - Google Cloud Functions に関するドキュメント
LogEntry : Cloud Logging - Google Cloud
GCP連載#1 Stackdriver Loggingへ良い感じのログ出力方法を考える - フューチャー技術ブログ
Ruby 3.0 型の静的解析機能について(RBS&TypeProf)
https://blog.orangeliner.net/tech/ruby3-rbs-typeprof.html
2021-01-12T00:00:00+09:00
朝霧
2020年12月に正式版がリリースされた、Ruby のメジャーアップデート「Ruby 3.0」において実装されている 静的解析 について調べてみました。
最近では爆速で走る静的型付け言語 Go も人気ですが、Ruby で導入された静的型付けはどのようなものなのでしょうか?
既存の型チェックgem
Rubype
TypeStruct
Ruby 3.0 での静的解析
RBS
TypeProf
まとめ
参考
既存の型チェックgem
ソースコード内に型注釈を記載し、値の型が合致しているかチェックする gem が既に存在しています。
とは言え、Ruby本体として型注釈を記載するのはあまり推奨されていないようなので、業務で書くプログラムに実際に導入するかとなると微妙なところもあるかもしれません…。
Rubype
http://gogotanaka.github.io/rubype.github.io/
メソッドの引数および戻り値の型(クラス)を指定する
Any型
ダックタイピング
シンボル
メソッド
指定したメソッドを呼び出し可能か
TypeStruct
https://github.com/ksss/type_struct
データの集合を型として定義する
Golang で謂う所の struct
Key が固定なら Struct、不定なら Hash を使うと良い
ArrayOf
Golang の []*type
Union
“どちらかの型” を指定(true or false、String or nil 等)
hash_from
Hash オブジェクトを TypeStruct オブジェクトに変換
Ruby において Hash のキーを typo すると値は nil が返るので、これによるバグが防げる
Ruby 3.0 での静的解析
Rubype や TypeStruct ではソースコード内で型をあらかじめ指定し、その型に合った値が入っているかをチェックする仕組みでした。
それに対し、Ruby 3.0 では、「型宣言なしで静的型チェック」を実現する第一歩として、RBS と TypeProf が gem として同梱されています。
なお、RBS および TypeProf は gem として提供されており、Ruby 3.0 以前でも gem install rbs / gem install typeprof で利用可能になります。
RBS
Ruby 3.0 に rbs gem が同梱
https://github.com/ruby/rbs
Rubyプログラムの型を記述するための言語
Union型 |
“どちらかの型” を指定
Optional型 ?
指定した型 or nil を許容
Tuples [ _type_ ]
要素の型が決まっている配列
Interface型
ダックタイピング
型の記述方法
rbs/syntax.md at master · ruby/rbs · GitHub
RBS による定義は拡張子を .rbs として、Ruby のソースコード自体とは別に作成するので、「型注釈の記載」とはならないという認識で良さそうです。
RBS の README.md に記載されているサンプルコードでは、クラス内に定義されたインスタンス変数やインスタンスメソッドに対する型指定が行われており、前述の Rubype および TypeStruct で実現していた機能はほぼ使えるようです。
ただし、Ruby では「型宣言なしで静的型チェック」を目指しているとの事ですので、RBS を自分で書いていては効率が悪いです。
そこで、次に紹介する TypeProf といった型解析ツールの出番です。
TypeProf
Ruby 3.0 に typeprof gem が同梱
https://github.com/ruby/typeprof
型推論を行うための型解析ツール
ソースコード内に定義されたメソッドについて、引数と戻り値の型を推論し、RBSフォーマットで出力する
こちらは 日本語ドキュメント も準備されており理解しやすいと思います。
TypeProf で Ruby のソースコードを読み込む事で、定義されているメソッドの引数および戻り値の型を推定して、RBS フォーマットで出力することができます。
ただし、現状は TypeProf 自体が実験的なプロダクトであり、特異メソッドの使用やメタプログラミング等においては制約もあるため、ドキュメントに記載があるように、意図した通りに型を推論できない場合もあります。
Ruby 3.0 で実装された静的解析を現時点で実際に使うとすれば、ソースコードを TypeProf で解析した結果の RBS を手動で確認・修正する手順が必要であると思われます。
まとめ
Ruby 3.0 では静的解析の実装に加え、パフォーマンスの改善および並行処理への最適化も実現されており、動的か静的かのベースの違いはあるものの、Go 言語との溝を着実に埋めようとしている印象を受けました。
静的解析について、使う or 使わない の選択肢が増えるのは良いことだと思います。
(使うとなった場合も、標準装備で使えるので)
Rubyでの型注釈は非推奨、でも動的な型周りのバグは防ぎたい…という事で Ruby の静的解析は”ニューノーマル”として定着するのか、今後のアップデートと共に注目していきたいと思います。
参考
Ruby に型があると便利か - るびま
Ruby 3.0.0 リリース(Ruby公式サイト)
あけましておめでとうございます
https://blog.orangeliner.net/article/2021-new-year.html
2021-01-01T00:00:00+09:00
朝霧
orangeliner.netから新年のご挨拶
今年はブログでのアウトプットも増やしていければと思います。
高さ15m超…毎年恒例、宝満神社(唐津市)の大門松
PythonでJSONを整形&記法チェック
https://blog.orangeliner.net/tech/python-check-json.html
2020-12-22T00:00:00+09:00
朝霧
Pythonの標準ライブラリを使ってCLIでサクッとJSONを整形&記法チェックする方法
Pythonの標準ライブラリである「JSONエンコーダ・デコーダ」のCLIを使って、JSONデータの整形表示や記法が正しいかをサクッとチェックすることができます。
LinuxやPowerShell等で、Pythonを実行出来る環境があればOK。
Pythonのプログラム外でも簡単に実行出来て便利です。
JSONを整形する
ファイルから入力
標準入力から入力
ファイルへ出力
JSONの記法チェック
サンプルファイル
sample1.json
改行が含まれない
文字列がUnicode
1
{"rabbit_house": [{"name": "\u30B3\u30B3\u30A2","age": 15,"dream": "\u8857\u306E\u56FD\u969B\u30D0\u30EA\u30B9\u30BF\u5F01\u8B77\u58EB"},{"name": "\u30C1\u30CE","age": 13,"dream": "\u30D0\u30EA\u30B9\u30BF"},{"name": "\u30EA\u30BC","age": 16,"dream": "\u5C0F\u5B66\u6821\u6559\u5E2B"}]}
sample2.json
誤った記法(10行目:”,”を2連続で入力)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"rabbit_house": [
{
"name": "\u30B3\u30B3\u30A2",
"age": 15,
"dream": "\u8857\u306E\u56FD\u969B\u30D0\u30EA\u30B9\u30BF\u5F01\u8B77\u58EB"
},
{
"name": "\u30C1\u30CE",
"age": 13,, // 誤った記法
"dream": "\u30D0\u30EA\u30B9\u30BF"
},
{
"name": "\u30EA\u30BC",
"age": 16,
"dream": "\u5C0F\u5B66\u6821\u6559\u5E2B"
}
]
}
JSONを整形する
改行・インデントが入って見易くなる!
Unicode文字列もデコードされ読める形式になる。
ファイルから入力
python -m json.tool {filename}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ python -m json.tool sample1.json
{
"rabbit_house": [
{
"name": "ココア",
"age": 15,
"dream": "街の国際バリスタ弁護士"
},
{
"name": "チノ",
"age": 13,
"dream": "バリスタ"
},
{
"name": "リゼ",
"age": 16,
"dream": "小学校教師"
}
]
}
標準入力から入力
{string} | python -m json.tool
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 文字列をパイプで渡す
$ '{"name": "チノ","age": 13,"dream": "バリスタ"}' | python -m json.tool
{
"name": "チノ",
"age": 13,
"dream": "バリスタ"
}
// catコマンドからパイプで渡す
$ cat sample1.json | python -m json.tool
{
"rabbit_house": [
{
"name": "ココア",
"age": 15,
"dream": "街の国際バリスタ弁護士"
},
...
}
ファイルへ出力
python -m json.tool {input_file} {output_file}
1
2
3
4
5
6
7
8
9
10
11
12
13
// sample1.json を整形して output.json に出力する
$ python -m json.tool sample1.json output.json
$ cat output.json
{
"rabbit_house": [
{
"name": "ココア",
"age": 15,
"dream": "街の国際バリスタ弁護士"
},
...
}
JSONの記法チェック
記法に誤りのあるJSONデータを読み込んだ場合、誤りのある箇所を表示してくれるので、エラー等の原因切り分けにも便利です。
1
2
3
$ python -m json.tool sample2.json
Expecting property name enclosed in double quotes: line 10 column 17 (char 203)
[Go] JSONファイル(ネストあり&配列型)を読み込む
https://blog.orangeliner.net/tech/golang-nested-json.html
2020-12-18T00:00:00+09:00
朝霧
Goでネスト構造を持つ&配列型のJSONファイルを読み込む方法
Goでネスト構造を持つ&配列型のJSONファイルを読み込む方法
読み込むJSONデータ
Goで読み込む
実行結果
[1] 構造体のフィールドとJSONのキーを紐付ける
[2] ネストしたJSONを読み込む構造体の定義
[3] 配列型の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データ内の配列の要素をこのスライスに格納する。