Serverless Frameworkの設定YAMLを、公式サイトの下記のようなサンプルを見ながら編集していました。
編集終わって実行してもうまくいかず、おかしいなとよく見てみると、本当は「arn: arn:xx」が「sns:」より1つインデントしていて、直すとうまくいきました…。
はずかしながら、実はYAMLが苦手で、理解が曖昧なまま何となく使っていたのですが、今回の件もあって、もう少しちゃんと使い方を知っておいた方がいいかなぁと思い、つまづいた所や勘違いしていた所を中心に、反省も踏まえ、使い方をまとめてみることにしました。
実際に書いてみないと分からないことも多く、下記のサイトでYAMLを入力しながら進めました。
YAMLとは?
YAMLとは、配列・マップのデータを表現するテキストフォーマットです。 人が感覚的に読みやすいことを考えて作られているのが特徴です。
配列
- 要素は「-」で始まり、スペースを開けて値を書く
- 並列する要素は、頭位置をそろえて縦に書く
YAML
- A - B - C
JSON
[ "A", "B", "C" ]
マップ
- 要素の後に「:」を書き、スペースを開けて値を書く
- 並列する要素は、頭位置をそろえて縦に書く
YAML
a: A b: B c: C
JSON
{ a: "A", b: "B", c: "C" }
コメント・区切り
- 「#」以降がコメント。どこでも書ける
- 「---」で区切り
YAML
- A # comment # comment - B - C --- - D - E - F
JSON
[ "A", "B", "C" ] [ "D", "E", "F" ]
値を次の行にずらせる
- 要素の値は次の行に書くことができる
- 値はインデントして要素の位置より下にしないといけない
- 例外(後述):マップの要素に配列が入る場合は、インデントしなくてもOK
YAML
- A - B - C
JSON
[ "A", "B", "C" ]
YAML
a: A b: B c: C
JSON
{ a: "A", b: "B", c: "C" }
入れ子
- 配列・マップをインデントで入れ子にできる
- 上記「値を次の行にずらせる」の応用とも言える
YAML
# 配列 -> 配列 - - A - B - C # 配列 -> マップ - a: A b: B - C # マップ -> 配列 a: - A - B c: C # マップ -> マップ a: b: B c: C d: D
JSON
[ ["A", "B"], "C" ] [ {a: "A", b: "B"}, "C" ] { a: ["A", "B"], c: "C" } { a: {b: "B", c: "C"}, d: "D" }
インデントが超重要
- 並列する要素はインデントの頭位置を揃える
- インデントの絶対位置は関係なく、各要素が周りと比較して、自分よりインデントが上か下か同じかで、自分がどこに属するかがきまる
深さに変わらず、下記の「A, B」と「C, D」は同じ位置になる。
YAML
- - A - B - - C - D
JSON
[ [ "A", "B" ], [ "C", "D" ], ]
1文字ずれただけで意味が違ってくる。下記は構文エラー
YAML
- - a - b - - c - d
インライン表記
- 最初に示したとおり、要素の直後に値を書ける。これをインライン表記と呼ぶ
- 要素の後に改行して、次の行以降に書くのをネクストライン表記と呼ぶ
YAML
- A # インライン表記 - B # ネクストライン表記 --- a: A # インライン表記 b: B # ネクストライン表記
JSON
[ "A", "B" ] { a: "A", b: "B" }
マップのインライン表記は、要素の値が直値(スカラー)の時のみ可能
YAML
# これはOK a: A # これはNG a: b: B # これもNG a: - A
配列のインライン表記は、配列・マップを値にすることも可能(使ってるの見たことないけど…)(並列する要素を書く時は、頭の位置をそろえる)
YAML
# これはOK - A # これもOK - a: A # これもOK - - A # 応用 - - A - B - - - c: C d: D - E
JSON
[ "A" ] [ { a: "A" } ] [ [ "A" ] ] [ [ "A", "B", [ [ { c: "C", d: "D" } ], "E" ] ] ]
インライン表記とネクストライン表記の両方を書くとおかしくなる
下記は構文エラー
YAML
a: A b: B
下記は意図していない値になる
YAML
- A - B
JSON
["A - B"]
インライン表記で値が何もない時はnullになる。(nullが省略されている)
YAML
- # nullが隠れている - B --- a: # nullが隠れている b: B
JSON
[ null, "B" ] { a: null, b: "B" }
nullは明示的に書いてもいい
YAML
- null - B --- a: null b: B
ネクストライン表記のインデントの例外
基本ネクストラインはインデントしないといけないが、マップの要素が配列の場合のみ、インデントしなくてもOK
YAML
a: - A # OK - B # OK b: - C - D
JSON
{ a: [ "A", "B" ], b: [ "C", "D" ] }
その逆、つまり、配列の値がマップの場合は、インデントしないとNG。下記は構文エラーとなる。
YAML
# [ {a: "A", b:"B"} ] を作りたかったとする - a: "A" # インデントしないといけない b: "B" # インデントしないといけない
フロースタイル
- そこだけJSON形式で埋め込められるモード
- 配列は「[]」でくくり、マップは「{}」でくくり、要素は「,」で区切る
- JSONなので、改行・インデントは気にしなくていい。普段JSONを書くのと同じ感覚で書ける
YAML
- [A, B, C] - a: { b: "B", c: "C" }
JSON
[ ["A", "B", "C"], { a: {b: "B", c: "C"} } ]
ひっかかりやすいポイントおさらい
- 向きは縦横どちらでもよくはない。必ず縦に並べる
- 並列する要素は、頭の位置を必ずそろえる
- インデント位置は重要。絶対位置(インデントの深さ)は重要でなく、相対位置が重要
- マップは直値以外はインライン表記できない
- インライン表記とネクストライン表記は同時に使ってはいけない
- インライン表記をすると、そこで要素の値が確定するので、続けてその要素の値を設定しようとするネクストライン表記を入れるのはNG
- nullは省略される
- 多くの場合、ミスによる意図しないnullが挿入されている
感想
今まで、インライン表記とネクストライン表記をよく分からないまま使っていて、それが混乱の原因の1つでした。
JSONは見やすくするために、適当にインデントさせたりするのですが、YAMLは見た目のためだけでなく、インデント自体が重要な意味を持っています。特に注意すべきなのは、高さを「そろえる」ということでしょうか。
YAMLの使い方は、簡潔にまとめられるかと思ったのですが、ルールを文章化しずらく、なんかモヤモヤするんですよねぇ…。
全ては、人が読みやすくするためにYAMLのルールを作ったことに起因するのですが、それが逆に人にとって分かりにくいルールになった、というのが自然言語らしくて面白いですね。
しかし、そのおかげで、設定ファイルなどの可読性は高くなりました。
ちなみに、最初のServerless Frameworkの設定ファイルの間違いについてですが、本来、「YAML_OK」であるべきものを、「YAML_NG」のように間違っていて、 スペースが1つ入っただけで全然別物になっています。更に、構文上はどちらも間違っていないのでやっかいです。(まさか勝手に「null」が入っているなんて思わないじゃないですか……。)
今なら多少は違いが分かるようになったので、少しはYAMLアレルギーを克服できたと思いたいですね。
YAML_OK
- sns: arn: arn:xxx # JSON # [ # { # sns: { arn: "arn:xxx" } # } # ]
YAML_NG
- sns: arn: arn:xxx # JSON # [ # { # sns: null # arn: "arn:xxx" # } # ]