pyてよn日記

一寸先は闇が人生

Python:YAML <-> Python オブジェクト <-> JSON の相互変換

最近,CSV ファイルから YAML 形式の設定ファイルを自動生成するスクリプトを書いていた.多重ネストされた YAML ファイルを Python オブジェクトとして読み込んだり,逆に Python オブジェクトを YAML ファイルに dump したりしようとする処理を書いていたところ,自分が実現したいファイルの構造を思うように生成できずにハマった.

忘れた頃に同じハマり方をしそうなので,いくつかの YAML ファイルの例とともに,YAML ファイル <-> Python の辞書型・リスト型の相互変換の方法をチートシートっぽくまとめた.加えて,「YAML ファイル <-> Python の辞書型・リスト型の相互変換」だけでなく,「YAML ファイル <-> JSON ファイルのオブジェクト・配列の相互変換」も扱った.YAMLJSON に共通するのは「階層的な構造」であり,両者は相互変換可能であるため,ここで扱って汎用的な知識にしておくのが良いと判断したからである(相互変換する状況があるかは別にして).

要は本記事で YAML ファイル <-> key-value 型,配列型の相互変換」がどのようになっているかが理解できれば良い.

※ かなり長いため,「なんもわからん」人(本当に YAML について何も分からない人)以外は目次から必要な内容を探して読むのが良い.

本記事の目標

本記事の目標は,以下 2 つの項目を理解できるようになることである.

  • 複雑にネストされた YAML <-> Python 辞書型・リスト型の相互変換
  • 複雑にネストされた YAML ファイル <-> key-value 型,配列型の相互変換

つまり,目標を 1 つにまとめると,以下になる.

  • YAML ファイルを見たときにその構造を適切に把握できるようになる

開発環境

実行環境

PyYAML は,PythonYAML ファイルを扱うためのパッケージである(デファクト?3rd パーティ製のパッケージをインストールするときに依存関係で見ることが多い気がする).標準モジュールでは無いためインストールする必要がある.

$ pip install PyYAML

pypi.org

使用ツール

  • Online YAML Parser
    • YAML ファイルのオンラインエディタ
    • YAML ファイルを編集すると瞬時にそれを JSONPython などに変換してくれる
    • YAML の構造をぱっと見渡したいときにまじ便利

YAML-online-parser.appspot.com

YAML の基本

まずは YAML の基本.

key: value <-> 辞書型

辞書型に変換される.ここでは対応する JSON として示すが,「オブジェクト = Python の辞書型」,「配列 = Python のリスト型」にそれぞれ置き換えれば OK.

key: value
{
  "key": "value"
}

- key: value <-> リスト型

-key: value の前に置くと,リスト型に変換される.つまり,- は配列(リスト)の 1 つの要素に対応する.

- key: value

key: value が配列(リスト)の一つの要素になっていることが分かる.

[
  {
    "key": "value"
  }
]

YAML ファイル内でのコメント

行の先頭に # を付ければコメントになる.

# コメント
key: value

複数行のコメントは無いため,複数行のコメントを行いたい場合は 1 行のコメントを連ねる必要がある.

# 複数行の
# コメントは
# 無い
key: value

改行

以下を参照.

インデント

YAML ファイルはインデントが揃っていないと読み込みエラーになる.読み込み時のエラーは大体インデントが原因.

インデントにタブ文字は使えないため注意.

サンプルを見てみる:docker-compose.yml

上述した辞書型とリスト型を基本とし,YAML ファイルは設定ファイルなどで利用される.

例として,筆者が手元で利用している docker-compose.yml を見てみる.ここでは,中身を理解する必要は全くなく,ただ YAML ファイルという「一定のルールで構造化されたテキストファイル」という観点で見れば良い.

この例で示すような「複雑にネストされた YAML <-> Python 辞書型・リスト型の変換」を自由自在に操れるようになるのが本記事の目標である.

version: "3"

services:
  db:
    container_name: atcoder-stream-db
    image: postgres:12.1
    expose:
      - "5432"

  web-backend:
    container_name: atcoder-stream-backend
    build:
      context: ./atcoder-stream-backend
      dockerfile: Dockerfile
    volumes:
      - ./atcoder-stream-backend/atcoder-stream-api:/app/src/atcoder-stream-api
      - ./atcoder-stream-backend/libraries/lib/lib:/app/src/libraries/lib
      - ./atcoder-stream-backend/libraries/twitterapi/twitterapi:/app/src/libraries/twitterapi
    ports:
      # host:container
      - "8000:8000"
    depends_on:
      - db
    command: sh -c "python /app/src/atcoder-stream-api/manage.py migrate && python /app/src/atcoder-stream-api/manage.py runserver 0.0.0.0:8000"

  web-frontend:
    container_name: atcoder-stream-frontend
    build:
      context: ./atcoder-stream-frontend
      dockerfile: Dockerfile
    volumes:
      - ./atcoder-stream-frontend:/app
    ports:
      # host:container
      - "3000:3000"
    command: sh -c "cd /app && yarn start"
{
  "version": "3",
  "services": {
    "db": {
      "container_name": "atcoder-stream-db",
      "image": "postgres:12.1",
      "expose": ["5432"]
    },
    "web-backend": {
      "container_name": "atcoder-stream-backend",
      "build": {
        "context": "./atcoder-stream-backend",
        "dockerfile": "Dockerfile"
      },
      "volumes": [
        "./atcoder-stream-backend/atcoder-stream-api:/app/src/atcoder-stream-api",
        "./atcoder-stream-backend/libraries/lib/lib:/app/src/libraries/lib",
        "./atcoder-stream-backend/libraries/twitterapi/twitterapi:/app/src/libraries/twitterapi"
      ],
      "ports": ["8000:8000"],
      "depends_on": ["db"],
      "command": "sh -c \"python /app/src/atcoder-stream-api/manage.py migrate && python /app/src/atcoder-stream-api/manage.py runserver 0.0.0.0:8000\""
    },
    "web-frontend": {
      "container_name": "atcoder-stream-frontend",
      "build": {
        "context": "./atcoder-stream-frontend",
        "dockerfile": "Dockerfile"
      },
      "volumes": ["./atcoder-stream-frontend:/app"],
      "ports": ["3000:3000"],
      "command": "sh -c \"cd /app && yarn start\""
    }
  }
}

ここで,YAMLJSON 両者を並べた画像を載せておく.各ファイルの構造がどのように対応しているかを確認しておくと良い.

f:id:pytwbf201830:20200515084244p:plain
YAMLJSON の対応関係

注意:本記事での用語の扱い方

本記事では,Python を中心に据えるため,YAML における key-value 型の表現を「辞書型」,配列型の表現を「リスト型」と表現する(単に言い換えが面倒なので).実際には,それぞれの言語,形式で呼び方が異なるが,本記事では全て「辞書型」,「リスト型」で統一する.

  • 実際に使われる用語と対応

正確には,「『YAML ファイルに定義された連想配列』を Python の辞書型に変換する」と言わなければいけないが,冗長になってしまうため,本記事では「『YAML ファイルの辞書型』を Python で読み込む」など簡易的な表現を使う.

実践例

ここからは,YAML ファイル <-> Python の変数の相互変換のさまざまな例を見ていく.各例について以下の 4 つを示し,相互変換の理解を深める.

  • YAML
    • YAML ファイルの例
  • JSON
    • YAML に対応する JSON ファイルの例
  • Python: load
    • YAML ファイルの例を Python の変数として読み込むためのコード
  • Python: dump
    • Python の変数を YAML ファイルとして dump するためのコード

ちなみに,本記事では YAML <-> Python が主であり,PythonJSON ファイルを扱う方法については触れていないため,それが知りたい方は手前味噌だが以下を参照すると良い.

pyteyon.hatenablog.com

ネストなし

YAML <-> 辞書型

  • YAML
    • no-nest-dict.yml
id: 3141592
name: pyteyon
main-language: Python
blog: プログラミング日記

上記 YAML ファイルのインライン表記

{ id: 3141592, name: pyteyon, main-language: Python, blog: プログラミング日記 }
{
  "id": 3141592,
  "name": "pyteyon",
  "main-language": "Python",
  "blog": "プログラミング日記"
}

参考

LIFE WITH PYTHON | Python Tips: JSON を整形して表示したい python で JSON を扱うとき,日本語をエスケープさせない方法(2014/03, Qiita) Python においての JSON ファイルの取扱いあれこれ(2018/07, Qiita)

pprint は辞書型,リスト型を整形して出力するためだけのもの)

import yaml
from pprint import pprint

with open(file='./no-nest-dict.yml', mode='r', encoding='utf-8') as f:
    dic = yaml.load(f)

pprint(dic, width=40)

""" 出力
{'blog': 'プログラミング日記',
 'id': 3141592,
 'main-language': 'Python',
 'name': 'pyteyon'}
"""
import yaml

data = {
    'id': 3141592,
    'main-language': 'Python',
    'name': 'pyteyon',
    'blog': 'プログラミング日記'
}

with open(file='./no-nest-dict.yml', mode='w', encoding='utf-8') as f:
    yaml.dump(
        data=data,
        stream=f,
        allow_unicode=True,  # Unicode 文字をデコードされた文字列で表現
        sort_keys=False
    )
補足:yaml.dump の キーワード引数

念のため,ここで利用する YAML.dump 関数の 2 つのキーワード引数を説明しておく.詳しくはドキュメントを読むと良い.

  • allow_unicode
    • Unicode 文字をそのまま扱えるようにする.
    • このオプションを,例えば日本語を使用している場合,エンコードされたままの Unicode 文字が出力されてしまう.このオプションを True にしておくことで,デコードされた Unicode 文字が出力されるようになる.
  • sort_keys
    • key でソートさせないようにする.
    • YAML.dump では dump する際に,辞書型の key をアルファベット順にソートしてしまう.自分が辞書型に登録した key の順序で YAML に出力させたい場合,このオプションを False にする.
allow_unicode
  • allow_unicode=False
id: 3141592
main-language: Python
name: pyteyon
blog: "\u30D7\u30ED\u30B0\u30E9\u30DF\u30F3\u30B0\u65E5\u8A18"
  • allow_unicode=True

デコードされた Unicode 文字が出力される

id: 3141592
main-language: Python
name: pyteyon
blog: プログラミング日記
sort_keys
  • sort_keys=False
id: 3141592
main-language: Python
name: pyteyon
blog: プログラミング日記
  • sort_keys=True

key でソートされた YAML ファイルが dump される.

blog: プログラミング日記
id: 3141592
main-language: Python
name: pyteyon

これ以降は,以下の設定を前提にする.

  • allow_unicode=TrueUnicode 文字はエンコードされていないものを dump したい)
  • sort_keys=False(key はこちらの意図した順序で dump したい)

YAML <-> リスト型

  • YAML
    • no-nest-list.yml
- id
- main-language
- name
- blog

インライン表記

["id", "main-language", "name", "blog"]
["id", "main-language", "name", "blog"]
import yaml
from pprint import pprint

with open(file='./no-nest-list.yml', mode='r', encoding='utf-8') as f:
    list_yaml = yaml.load(f)

pprint(list_yaml, width=40)

""" 出力
['id', 'main-language', 'name', 'blog']
"""
import yaml

data = {
    'id': 3141592,
    'main-language': 'Python',
    'name': 'pyteyon',
    'blog': 'プログラミング日記'
}

with open(file='./no-nest-dict.yml', mode='w', encoding='utf-8') as f:
    yaml.dump(
        data=data,
        stream=f,
        allow_unicode=True,
        sort_keys=False
    )

読み込みエラー:同じ階層に辞書型・リスト型を置く

YAML ファイル内で辞書型・リスト型に変換される表現を同じ階層に置くと,読み込み時にエラーが起こる.

  • YAML(読み込みエラーになる)
    • no-nest-dict-and-list.yml
# 辞書型に変換される表現
id: 3141592
main-language: Python

# リスト型に変換される表現
- name: pyteyon
- blog: プログラミング日記
  • JSON(読み込みエラーになる)

2 つの要素になってしまっている.

{
  "id": 3141592,
  "main-language": "Python",
}
[
    {"name": "pyteyon"},
    {"blog": "プログラミング日記"},
]

ParseError になる.

import yaml

with open(file='./no-nest-dict-and-list.yml', mode='r', encoding='utf-8') as f:
    obj = yaml.load(f)

""" 出力
ParserError: while parsing a block mapping
  in "./no-nest/no-nest-dict-and-list.yml", line 2, column 1
expected <block end>, but found '-'
  in "./no-nest/no-nest-dict-and-list.yml", line 6, column 1
"""

上記のエラーから分かるように,YAML ファイルは必ず「辞書型,またはリスト型のどちらかの型に変換可能な表現」でなければならない.つまり,変換の際,ネストの最上位は必ず {} または [] で囲われた形になる(JSON もこれ).

尚,以下の形は可能である.

  • 辞書型の key,value にリスト型が使われる
  • リスト型の中身に辞書型が使われる

ネストの最上位に二つの要素が存在することがなければ問題なく読み込むことができる.

ネストあり:2 重ネスト

辞書型

dict-in-dict:辞書型の要素に辞書型

辞書型の要素に辞書型を使う.インデントのみで辞書型の要素を辞書型にできる.

  • YAML
    • nest-2-dict-in-dict.yml
id: 3141592
name: pyteyon
twitter-profile:
  screen-name: mathnuko
  username: mathnuko
  start: March 2018
  tweets: 5596

インライン表記

id: 3141592
name: pyteyon
twitter-profile: # 改行できる
  { screen-name: mathnuko, username: mathnuko, start: March 2018, tweets: 5596 }
{
  "id": 3141592,
  "name": "pyteyon",
  "twitter-profile": {
    "screen-name": "mathnuko",
    "username": "mathnuko",
    "start": "March 2018",
    "tweets": 5596
  }
}
import yaml
from pprint import pprint

with open(file='./nest-2-dict-in-dict.yml', mode='r', encoding='utf-8') as f:
    obj = yaml.load(f)

pprint(obj, width=40)

""" 出力
{'id': 3141592,
 'name': 'pyteyon',
 'twitter-profile': {'screen-name': 'mathnuko',
                     'start': 'March '
                              '2018',
                     'tweets': 5596,
                     'username': 'mathnuko'}}
"""
import yaml

data = {
    'experienced-languages': [
        'Python',
        'JavaScript',
        'C++'
    ],
    'id': 3141592,
    'name': 'pyteyon'
}

with open(file='./nest-2-dict-in-dict.yml`', mode='w', encoding='utf-8') as f:
    yaml.dump(
        data=data,
        stream=f,
        allow_unicode=True,
        sort_keys=False
    )
list-in-dict:辞書型の要素にリスト型

辞書型の要素にリスト型を使う.

  • YAML
    • nest-2-list-in-dict.yml
id: 3141592
name: mathnuko
experienced-languages:
  - Python
  - JavaScript
  - C++

インライン表記

id: 3141592
name: mathnuko
experienced-languages: [Python, JavaScript, C++]
{
    "id": 3141592,
    "name": "mathnuko",
    "experienced-languages": [
        "Python",
        "JavaScript",
        "C++"
    ]
}
import yaml
from pprint import pprint

with open(file='./nest-2-list-in-dict.yml', mode='r', encoding='utf-8') as f:
    obj = yaml.load(f)

pprint(obj, width=40)

""" 出力
{'experienced-languages': ['Python',
                           'JavaScript',
                           'C++'],
 'id': 3141592,
 'name': 'pyteyon'}
"""
import yaml

data = {
    'experienced-languages': [
        'Python',
        'JavaScript',
        'C++'
    ],
    'id': 3141592,
    'name': 'pyteyon'
}

with open(file='./nest-2-list-in-dict.yml', mode='w', encoding='utf-8') as f:
    yaml.dump(
        data=data,
        stream=f,
        allow_unicode=True,
        sort_keys=False
    )

リスト型

dict-in-list:リスト型の要素に辞書型
  • YAML
    • nest-2-dict-in-list.yml
- id: 3141592
- main-language: Python
- name: pyteyon
- blog: プログラミング日記

インライン表記

[id: 3141592, main-language: Python, name: pyteyon, blog: プログラミング日記]
[
    {
        "id": 3141592
    },
    {
        "main-language": "Python"
    },
    {
        "name": "pyteyon"
    },
    {
        "blog": "プログラミング日記"
    }
]
import yaml
from pprint import pprint

with open(file='./nest-2-dict-in-list.yml', mode='r', encoding='utf-8') as f:
    obj = yaml.load(f)

pprint(obj, width=40)

""" 出力
[{'id': 3141592},
 {'main-language': 'Python'},
 {'name': 'pyteyon'},
 {'blog': 'プログラミング日記'}]
"""
import yaml

data = [
    {'id': 3141592},
    {'main-language': 'Python'},
    {'name': 'pyteyon'},
    {'blog': 'プログラミング日記'}
]

with open(file='./nest-2-dict-in-list.yml', mode='w', encoding='utf-8') as f:
    yaml.dump(
        data=data,
        stream=f,
        allow_unicode=True,
        sort_keys=False
    )
list-in-list:リスト型の要素にリスト型
  • YAML
    • nest-2-list-in-list.yml
- - Python
  - JavaScript
  - Ruby
- - Django
  - Flask
  - Express
  - Ruby on Rails
- - unittest
  - pytest
  - Jest
  - RSpec

インライン表記

- [Python, JavaScript, Ruby]
- [Django, Flask, Express, Ruby on Rails]
- [unittest, pytest, Jest, Rspec]
[
    [
        "Python",
        "JavaScript",
        "Ruby"
    ],
    [
        "Django",
        "Flask",
        "Express",
        "Ruby on Rails"
    ],
    [
        "unittest",
        "pytest",
        "Jest",
        "Rspec"
    ]
]
import yaml
from pprint import pprint

with open(file='./nest-2-list-in-list.yml', mode='r', encoding='utf-8') as f:
    obj = yaml.load(f)

pprint(obj, width=40)

""" 出力
[['Python', 'JavaScript', 'Ruby'],
 ['Django',
  'Flask',
  'Express',
  'Ruby on Rails'],
 ['unittest',
  'pytest',
  'Jest',
  'Rspec']]
"""
import yaml

data = [
    ['Python', 'JavaScript', 'Ruby'],
    ['Django', 'Flask', 'Express', 'Ruby on Rails'],
    ['unittest', 'pytest', 'Jest', 'Rspec']
]

with open(file='./nest-2-list-in-list.yml', mode='w', encoding='utf-8') as f:
    yaml.dump(
        data=data,
        stream=f,
        allow_unicode=True,
        sort_keys=False
    )

ネストあり:3 重以上のネスト

最後に,3 重以上のネストがある YAML ファイルを扱う.

3 重以上のネストがある YAML ファイルでは,非常に多くの組み合わせがあり複雑さも増すため,体系的にまとめずにいくつかの例を示すことにする.

ただし,ネストの複雑さが増すと言っても,結局はここまでで扱った単純な構造の組み合わせであるため,構造を丁寧に分解していけば尻込みすることはない.

例 1:docker-compose.yml(再掲)

  • YAML
    • docker-compose.yml
version: "3"

services:
  db:
    container_name: atcoder-stream-db
    image: postgres:12.1
    expose:
      - "5432"

  web-backend:
    container_name: atcoder-stream-backend
    build:
      context: ./atcoder-stream-backend
      dockerfile: Dockerfile
    volumes:
      - ./atcoder-stream-backend/atcoder-stream-api:/app/src/atcoder-stream-api
      - ./atcoder-stream-backend/libraries/lib/lib:/app/src/libraries/lib
      - ./atcoder-stream-backend/libraries/twitterapi/twitterapi:/app/src/libraries/twitterapi
    ports:
      - "8000:8000"
    depends_on:
      - db
    command: sh -c "python /app/src/atcoder-stream-api/manage.py migrate && python /app/src/atcoder-stream-api/manage.py runserver 0.0.0.0:8000"

  web-frontend:
    container_name: atcoder-stream-frontend
    build:
      context: ./atcoder-stream-frontend
      dockerfile: Dockerfile
    volumes:
      - ./atcoder-stream-frontend:/app
    ports:
      - "3000:3000"
    command: sh -c "cd /app && yarn start"
{
  "version": "3",
  "services": {
    "db": {
      "container_name": "atcoder-stream-db",
      "image": "postgres:12.1",
      "expose": ["5432"]
    },
    "web-backend": {
      "container_name": "atcoder-stream-backend",
      "build": {
        "context": "./atcoder-stream-backend",
        "dockerfile": "Dockerfile"
      },
      "volumes": [
        "./atcoder-stream-backend/atcoder-stream-api:/app/src/atcoder-stream-api",
        "./atcoder-stream-backend/libraries/lib/lib:/app/src/libraries/lib",
        "./atcoder-stream-backend/libraries/twitterapi/twitterapi:/app/src/libraries/twitterapi"
      ],
      "ports": ["8000:8000"],
      "depends_on": ["db"],
      "command": "sh -c \"python /app/src/atcoder-stream-api/manage.py migrate && python /app/src/atcoder-stream-api/manage.py runserver 0.0.0.0:8000\""
    },
    "web-frontend": {
      "container_name": "atcoder-stream-frontend",
      "build": {
        "context": "./atcoder-stream-frontend",
        "dockerfile": "Dockerfile"
      },
      "volumes": ["./atcoder-stream-frontend:/app"],
      "ports": ["3000:3000"],
      "command": "sh -c \"cd /app && yarn start\""
    }
  }
}
data = {
    'version': '3',
    'services': {
        'db': {
            'container_name': 'atcoder-stream-db',
            'expose': ['5432'],
            'image': 'postgres:12.1'
        },
        'web-backend': {
            'build': {
                'context': './atcoder-stream-backend',
                'dockerfile': 'Dockerfile'
            },
            'command': 'sh '
            '-c '
            '"python '
            '/app/src/atcoder-stream-api/manage.py '
            'migrate '
            '&& '
            'python '
            '/app/src/atcoder-stream-api/manage.py '
            'runserver '
            '0.0.0.0:8000"',
            'container_name': 'atcoder-stream-backend',
            'depends_on': ['db'],
            'ports': ['8000:8000'],
            'volumes': [
                './atcoder-stream-backend/atcoder-stream-api:/app/src/atcoder-stream-api',
                './atcoder-stream-backend/libraries/lib/lib:/app/src/libraries/lib',
                './atcoder-stream-backend/libraries/twitterapi/twitterapi:/app/src/libraries/twitterapi'
            ]
        },
        'web-frontend': {
            'build': {
                'context': './atcoder-stream-frontend',
                'dockerfile': 'Dockerfile'
            },
            'command': 'sh '
            '-c '
            '"cd '
            '/app '
            '&& '
            'yarn '
            'start"',
            'container_name': 'atcoder-stream-frontend',
            'ports': ['3000:3000'],
            'volumes': ['./atcoder-stream-frontend:/app']
        }
    }
}

例 2:language-list.yml

プログラミング言語のリストを表す YAML ファイルを作ってみた.遊びで書いたので内容は間違いがあるかもしれない.

  • YAML
    • language-list.yml
# programming language list
- language: Python
  information:
    creator: Guido van Rossum
    first-release: 1991
    official-page: https://www.python.org/
  features:
    type-system: dynamic
    paradigm:
      - object-oriented # オブジェクト指向
      - imperative # 命令型
      - procedural # 手続き型(=命令型)
    memory: managed
    web-framework:
      - name: Django
        type: full-stack
        documentation: https://docs.djangoproject.com/en/3.0/
      - name: Flask
        type: micro
        documentation: https://flask.palletsprojects.com/en/1.1.x/
      - name: FastAPI
        type: micro
        documentation: https://fastapi.tiangolo.com/
    test-framework:
      - unittest
      - pytest
      - nose
- language: Ruby
  information:
    creator: Yukihiro "Matz" Matsumoto
    first-release: 1995
    official-page: https://www.ruby-lang.org/en/
  features:
    type-system: dynamic
    paradigm:
      - object-oriented
      - imperative
      - procedural
    memory: managed
    web-framework:
      - name: Ruby on Rails
        type: full-stack
        documentation: https://rubyonrails.org/
      - name: Sinatra
        type: micro
        documentation: http://sinatrarb.com/
      - name: Cuba
        type: micro
        documentation: https://cuba.is/
    test-framework:
      - RSpec
      - "Test::Unit"
      - Cucumber
- language: JavaScript
  information:
    creator: Brendan Eich
    first-release: 1997
    official-page: https://www.ecma-international.org/ecma-262/
    developer-page: https://developer.mozilla.org/en/JavaScript
  features:
    type-system: dynamic
    paradigm:
      - object-oriented
      - imperative
      - procedural
    memory: managed
    framework:
      backend:
        - name: Express.js
          type: micro
          documentation: http://expressjs.com/
        - name: Meteor.js
          type: micro
          documentation: https://www.meteor.com/
      frontend:
        - name: React.js
          documentation: https://ja.reactjs.org/
        - name: Vue.js
          documentation: https://vuejs.org/index.html
        - name: Angular.js
          documentation: https://angularjs.org/
    test-framework:
      backend:
        - Mocha
        - AVA
      frontend:
        - Jest
        - Jasmine
- language: Java
  information:
    creator: James Arthur Gosling(Sun Microsystems, merged to Oracle in 2010)
    first-release: 1995
    official-page: https://www.oracle.com/technetwork/java/javase/documentation/index.html
  features:
    type-system: static
    paradigm:
      - object-oriented
      - imperative
      - procedural
    memory: managed
    web-framework:
      - name: Spring Framework
        type: full-stack
        documentation: https://projects.spring.io/spring-framework/
      - name: Play Framework
        type: full-stack
        documentation: https://www.playframework.com/
        inspired:
          - language: Ruby
            framework: Ruby on Rails
          - language: Python
            framework: Django
      - name: Struts
        type: full-stack
        documentation: https://struts.apache.org/
    test-framework:
      - JUnit
      - TestNG
[
  {
    "language": "Python",
    "information": {
      "creator": "Guido van Rossum",
      "first-release": 1991,
      "official-page": "https://www.python.org/"
    },
    "features": {
      "type-system": "dynamic",
      "paradigm": ["object-oriented", "imperative", "procedural"],
      "memory": "managed",
      "web-framework": [
        {
          "name": "Django",
          "type": "full-stack",
          "documentation": "https://docs.djangoproject.com/en/3.0/"
        },
        {
          "name": "Flask",
          "type": "micro",
          "documentation": "https://flask.palletsprojects.com/en/1.1.x/"
        },
        {
          "name": "FastAPI",
          "type": "micro",
          "documentation": "https://fastapi.tiangolo.com/"
        }
      ],
      "test-framework": ["unittest", "pytest", "nose"]
    }
  },
  {
    "language": "Ruby",
    "information": {
      "creator": "Yukihiro \"Matz\" Matsumoto",
      "first-release": 1995,
      "official-page": "https://www.ruby-lang.org/en/"
    },
    "features": {
      "type-system": "dynamic",
      "paradigm": ["object-oriented", "imperative", "procedural"],
      "memory": "managed",
      "web-framework": [
        {
          "name": "Ruby on Rails",
          "type": "full-stack",
          "documentation": "https://rubyonrails.org/"
        },
        {
          "name": "Sinatra",
          "type": "micro",
          "documentation": "http://sinatrarb.com/"
        },
        {
          "name": "Cuba",
          "type": "micro",
          "documentation": "https://cuba.is/"
        }
      ],
      "test-framework": ["RSpec", "Test::Unit", "Cucumber"]
    }
  },
  {
    "language": "JavaScript",
    "information": {
      "creator": "Brendan Eich",
      "first-release": 1997,
      "official-page": "https://www.ecma-international.org/ecma-262/",
      "developer-page": "https://developer.mozilla.org/en/JavaScript"
    },
    "features": {
      "type-system": "dynamic",
      "paradigm": ["object-oriented", "imperative", "procedural"],
      "memory": "managed",
      "framework": {
        "backend": [
          {
            "name": "Express.js",
            "type": "micro",
            "documentation": "http://expressjs.com/"
          },
          {
            "name": "Meteor.js",
            "type": "micro",
            "documentation": "https://www.meteor.com/"
          }
        ],
        "frontend": [
          {
            "name": "React.js",
            "documentation": "https://ja.reactjs.org/"
          },
          {
            "name": "Vue.js",
            "documentation": "https://vuejs.org/index.html"
          },
          {
            "name": "Angular.js",
            "documentation": "https://angularjs.org/"
          }
        ]
      },
      "test-framework": {
        "backend": ["Mocha", "AVA"],
        "frontend": ["Jest", "Jasmine"]
      }
    }
  },
  {
    "language": "Java",
    "information": {
      "creator": "James Arthur Gosling(Sun Microsystems, merged to Oracle in 2010)",
      "first-release": 1995,
      "official-page": "https://www.oracle.com/technetwork/java/javase/documentation/index.html"
    },
    "features": {
      "type-system": "static",
      "paradigm": ["object-oriented", "imperative", "procedural"],
      "memory": "managed",
      "web-framework": [
        {
          "name": "Spring Framework",
          "type": "full-stack",
          "documentation": "https://projects.spring.io/spring-framework/"
        },
        {
          "name": "Play Framework",
          "type": "full-stack",
          "documentation": "https://www.playframework.com/",
          "inspired": [
            {
              "language": "Ruby",
              "framework": "Ruby on Rails"
            },
            {
              "language": "Python",
              "framework": "Django"
            }
          ]
        },
        {
          "name": "Struts",
          "type": "full-stack",
          "documentation": "https://struts.apache.org/"
        }
      ],
      "test-framework": ["JUnit", "TestNG"]
    }
  }
]
language_list = [
    # Python
    {
        'language': 'Python',
        'information': {
            'creator': 'Guido van Rossum',
            'first-release': 1991,
            'official-page': 'https://www.python.org/'
        },
        'features': {
            'memory': 'managed',

            'paradigm': [
                'object-oriented',
                'imperative',
                'procedural'
            ],
            'test-framework': [
                'unittest',
                'pytest',
                'nose'
            ],
            'type-system': 'dynamic',
            'web-framework': [
                {
                    'documentation': 'https://docs.djangoproject.com/en/3.0/',
                    'name': 'Django',
                    'type': 'full-stack'
                },
                {
                    'documentation': 'https://flask.palletsprojects.com/en/1.1.x/',
                    'name': 'Flask',
                    'type': 'micro'
                },
                {
                    'documentation': 'https://fastapi.tiangolo.com/',
                    'name': 'FastAPI',
                    'type': 'micro'
                }
            ]
        },
    },
    # Ruby
    {
        'language': 'Ruby',
        'information': {
            'creator': 'Yukihiro "Matz" Matsumoto',
            'first-release': 1995,
            'official-page': 'https://www.ruby-lang.org/en/'
        },
        'features': {
            'memory': 'managed',
            'paradigm': [
                'object-oriented',
                'imperative',
                'procedural'
            ],
            'test-framework': [
                'RSpec',
                'Test::Unit',
                'Cucumber'
            ],
            'type-system': 'dynamic',
            'web-framework': [
                {
                    'documentation': 'https://rubyonrails.org/',
                    'name': 'Ruby '
                    'on '
                    'Rails',
                    'type': 'full-stack'
                },
                {
                    'documentation': 'http://sinatrarb.com/',
                    'name': 'Sinatra',
                    'type': 'micro'
                },
                {
                    'documentation': 'https://cuba.is/',
                    'name': 'Cuba',
                    'type': 'micro'
                }
            ]
        },
    },
    # JavaScript
    {
        'language': 'JavaScript',
        'information': {
            'creator': 'Brendan Eich',
            'developer-page': 'https://developer.mozilla.org/en/JavaScript',
            'first-release': 1997,
            'official-page': 'https://www.ecma-international.org/ecma-262/'
        },
        'features': {
            'framework': {
                'backend': [
                    {
                        'documentation': 'http://expressjs.com/',
                        'name': 'Express.js',
                        'type': 'micro'
                    },
                    {
                        'documentation': 'https://www.meteor.com/',
                        'name': 'Meteor.js',
                        'type': 'micro'
                    }
                ],
                'frontend': [
                    {
                        'documentation': 'https://ja.reactjs.org/',
                        'name': 'React.js'
                    },
                    {
                        'documentation': 'https://vuejs.org/index.html',
                        'name': 'Vue.js'
                    },
                    {
                        'documentation': 'https://angularjs.org/',
                        'name': 'Angular.js'
                    }
                ]
            },
            'memory': 'managed',
            'paradigm': [
                'object-oriented',
                'imperative',
                'procedural'
            ],
            'test-framework': {
                'backend': [
                    'Mocha',
                    'AVA'
                ],
                'frontend': [
                    'Jest',
                    'Jasmine'
                ]
            },
            'type-system': 'dynamic'
        },
    },
    # Java
    {
        'language': 'Java',
        'information': {
            'creator': 'James Arthur Gosling(Sun Microsystems, merged to Oracle in 2010)',
            'first-release': 1995,
            'official-page': 'https://www.oracle.com/technetwork/java/javase/documentation/index.html'
        },
        'features': {
            'memory': 'managed',
            'paradigm': [
                'object-oriented',
                'imperative',
                'procedural'
            ],
            'test-framework': [
                'JUnit',
                'TestNG'
            ],
            'type-system': 'static',
            'web-framework': [
                {
                    'documentation': 'https://projects.spring.io/spring-framework/',
                    'name': 'Spring '
                    'Framework',
                    'type': 'full-stack'
                },
                {
                    'documentation': 'https://www.playframework.com/',
                    'inspired': [
                        {
                            'framework': 'Ruby '
                            'on '
                            'Rails',
                            'language': 'Ruby'
                        },
                        {
                            'framework': 'Django',
                            'language': 'Python'
                        }
                    ],
                    'name': 'Play '
                    'Framework',
                    'type': 'full-stack'
                },
                {
                    'documentation': 'https://struts.apache.org/',
                    'name': 'Struts',
                    'type': 'full-stack'
                }
            ]
        },
    }
]

終わりに

複雑な YAML ファイルも単純な構造の組み合わせであると分かれば難しくはない.

内容の割りにかなりのボリュームなってしまったが,自分がハマったときに戻ってこれるチートシート的な記事が書けたので満足.長い記事に付き合ってくださった方はありがとうございました.

参考

特に 1 番上の記事は非常に参考になった.

magazine.rubyist.net

blog.sus-happy.net

ja.wikipedia.org