ラビリン「ヒープリのミニコーナーデータの構造をJSON Schemaで書いてみるラビ！」

ラビリン「今日は『ヒーリングっど♥プリキュア』のミニコーナーのデータ構造をJSON Schemaで書いてみるラビ！」
ニャトラン「良いけどよ…ニャンだそれ？」

# お断り
本記事の内容は、2020年6月29日（「ヒーリングっど♥プリキュア」本編第13話まで[^osarai]放送済み）時点での状況を元に記載しています。
以下、特記のない限り「現在」はこの日時点をさします。

[^osarai]: 新型コロナウイルス感染症に対する安全対策への最大限の配慮をすることを目的に、2020年4月26日から6月21日まで、通常の放送を休止し、「おさらいセレクション」と題して通常の放送枠（テレ朝系24局：日曜日午前8時30分、BSS山陰放送：土曜日午前11時15分（遅れネット））で再放送を実施しました。6月28日以降、第13話から通常通りの放送に戻っています。

# 2020/05/16更新

ラビリン「最近『[JSON Schema で楽して良い感じの設定画面を作ろう！ ～ Vue.js 編 ～](https://qiita.com/yokra9/items/e4907e319b6f6c58b404)』という記事を見たラビ」
ラビリン「そうしたらこう書いてあったラビ」

```json:customer.json
{
    "$schema": "https://raw.githubusercontent.com/Sequoia/loopback-json-schemas/master/dist/loopback-model-definition.json"
}
```
> `$schema` プロパティの値として、JSON Schema ファイルの場所を指定します。

ニャトラン「 `$schema` プロパティを設定するのを忘れてたニャ！」
ラビリン「ということで修正したラビ！」
ペギタン「スキーマ定義も変わってるから注意してほしいペエ」

# 用語集
* ヒープリ：『ヒーリングっど♥プリキュア』のこと<br />
  →[制作局（朝日放送テレビ）の公式サイト](http://www.asahi.jp/precure/)<br />
  →[制作アニメ会社（東映アニメーション）の公式サイト](http://www.toei-anim.co.jp/tv/healingood_precure/)
* ミニコーナー：番組最後のコーナー。前作『スター☆トゥインクルプリキュア』では毎週星座占いを放送していました。<br />
  この星座占いのデータ（特別版となった第49話（最終話）を除く）を[hokkaidosm.net プリキュアデータ](https://precure.hokkaidosm.net)で公開しています。<br />
  →[スター☆トゥインクルプリキュア　星座占いまとめ | hokkaidosm.net プリキュアデータ](https://precure.hokkaidosm.net/star_twincle/fortune.php)

# JSON Schemaとは
ペギタン「ところで…JSON Schemaってなにペエ？」
ラビリン「[公式サイト](http://json-schema.org/)があるから見てみるラビ！」

> JSON Schema is a vocabulary that allows you to annotate and validate JSON documents.

> The JSON document being validated or described we call the instance, and the document containing the description is called the schema.

※以上引用部の出典：[JSON Schema | The home of JSON Schema](http://json-schema.org/)

ラビリン「英語全然わからないラビ…」
ニャトラン「こんな記事見つけたぜ！」

> そもそも JSON Schema とは何やねん！ となる方も多いと思うので簡単に解説しておくと、
json-schema.orgによって開発されているJSON objectを記述したり検証したりするための設計書のようなものです。

※以上引用部の出典：[JSON Schemaと友達になる為の記事 - Qiita](https://qiita.com/arumi8go/items/a9530cbd39ff545a7bbb)

ラビリン「 @arumi8go さんありがとうラビ！」

# ミニコーナーの種類

ペギタン「まずミニコーナーにはどんなのがあるか整理してみるペエ」

## エレメントさんどーこだ？

ラビリン「どこかに隠れているエレメントさんを探し出すラビ！」
ラビリン「第1話のミニコーナーは https://youtu.be/EK_4UQPsqgg で見ることができるラビ」
ペギタン「必要なデータはこんな感じペエ」

| 項目物理名 |         項目名         |                      内容                      |   例（第1話の値）  |
|:-----------|:-----------------------|:-----------------------------------------------|:-------------------|
| animal     | 担当ヒーリングアニマル | この回の進行を担当するヒーリングアニマルの名前 | ラビリン           |
| element    | 登場エレメントさん     | この回で探すエレメントさんの名前               | 花のエレメントさん |
| place      | 場所                   | この回で探す場所の名前                         | のどかの家         |

## パートナーをさがせ！

ペギタン「僕たちがどこにいるかあててほしいペエ」
ペギタン「第2話のミニコーナーは https://youtu.be/pJGO8Ka71sk で見ることができるペエ」
ニャトラン「必要なデータはこんな感じかニャ？」

| 項目物理名 |         項目名         |                      内容                      | 例（第2話の値） |
|:-----------|:-----------------------|:-----------------------------------------------|:----------------|
| animal     | 担当ヒーリングアニマル | この回の進行を担当するヒーリングアニマルの名前 | ペギタン        |

## ほんものはどっち？

ニャトラン「ほんもののエレメントさんはどっちか当てるニャ！」
ニャトラン「第3話のミニコーナーは https://youtu.be/SCz6SJFH4as で見ることができるぜ！」
ラビリン「必要なデータはこんな感じラビね」

| 項目物理名 |         項目名         |                      内容                      |   例（第3話の値）  |
|:-----------|:-----------------------|:-----------------------------------------------|:-------------------|
| animal     | 担当ヒーリングアニマル | この回の進行を担当するヒーリングアニマルの名前 | ニャトラン         |
| element    | 登場エレメントさん     | この回のお題のエレメントさんの名前             | 水のエレメントさん |

## てあらい・うがい

ラビリン「第11話・第12話（4月12日・4月19日本放送回）とおさらいセレクション（第7話まで[^osaraiep]）はこの3つのゲームの代わりに、プリキュアたちがてあらい・うがいをしよう！って呼びかけているムービーを放送しているラビ！」

[^osaraiep]: おさらいセレクション第8話（6月14日放送）は「パートナーをさがせ！（ラビリン）」を、第12話（6月21日放送）は「ほんものはどっち？（ニャトラン / 泡のエレメントさん）」を放送

## まとめ

ラビリン「ということで、ミニコーナーとしては次の4つを扱うことにするラビ！」

* エレメントさんどーこだ？
* パートナーをさがせ！
* ほんものはどっち？
* てあらい・うがい

# 各回共通のデータ

ラビリン「ミニコーナーのデータの前に、各回で持つ共通のデータを定義するラビ！」

|  項目物理名  |      項目名      |                                内容                               |      例（第1話の値）     |
|:-------------|:-----------------|:------------------------------------------------------------------|:-------------------------|
| episode_code | 放送回           | この回の放送回。おさらいセレクション[^osarai]は（再）を頭につける | 第1話                    |
| onair_date   | 放送日           | この回の放送日。yyyy-MM-dd形式                                    | 2020-02-02               |
| type         | ミニコーナー種別 | この回のミニコーナー種別                                          | エレメントさんどーこだ？ |

ラビリン「この3つは必須にするラビ！」
ニャトラン「これをJSON Schemaで表すとどうなるんだニャ？」
ラビリン「こうなるラビ！」

```json:mini_corner.schema.json（の一部）
            "episode_code": {
                "type": "string",
                "title": "放送回",
                "description": "放送回　再放送の場合は頭に「（再）」をつける",
                "examples": [
                    "第1話",
                    "（再）第1話"
                ]
            },
            "onair_date": {
                "type": "string",
                "format": "date",
                "title": "放送日",
                "examples": [
                    "2020-02-02",
                    "2020-04-26"
                ]
            },
            "type": {
                "type": "string",
                "enum": ["エレメントさんどーこだ？", "パートナーをさがせ！", "ほんものはどっち？", "てあらい・うがい"],
                "title": "ミニコーナー種別"
            },
```

ニャトラン「ニャるほど！ `type` でそのデータの型を決めて、 `title` と `description` でそのデータを説明しているんだニャ」
ペギタン「 `examples` はそのデータの例を示しているんだペエ」
ラビリン「そして `type` の `enum` で、さっきのミニコーナー以外の値を受け付けないようにしているラビ」
ペギタン「ところで必須ってどこに書いてあるペエ？」
ラビリン「それは後で出てくるラビ！」

# ミニコーナーデータをJSON Schemaで表してみる

ラビリン「次は各ミニコーナーで使用するデータをJSON Schemaで表してみるラビ！」

```json:mini_corner.schema.json（の一部）
            "animal": {
                "type": ["string", "null"],
                "enum": ["ラビリン", "ペギタン", "ニャトラン", null],
                "title": "担当ヒーリングアニマル",
                "description": "「てあらい・うがい」の場合は設定しない"
            },
            "element": {
                "type": ["string", "null"],
                "title": "登場エレメントさん",
                "description": "「エレメントさんどーこだ？」「ほんものはどっち？」の場合指定",
                "examples": [
                    "花のエレメントさん",
                    "水のエレメントさん",
                    "光のエレメントさん"
                ]
            },
            "place": {
                "type": ["string", "null"],
                "title": "探す場所",
                "description": "「エレメントさんどーこだ？」の場合指定",
                "examples": [
                    "旅館沢泉のペットと入れる温泉",
                    "のどかの家"
                ]
            }
```

ペギタン「なんで `type` が `["string", "null"]` なんだペエ？」
ラビリン「それは、文字列か `null` が入ることを示しているラビ」

# 項目の有無とミニコーナーを紐付ける

ニャトラン「そういえば「パートナーをさがせ！」の時は `element` と `place` がいらないとかはどう定義するんだニャ？」
ラビリン「こう定義するラビ！」

<details><summary>長いので折りたたむラビ</summary><div>

```json:mini_corner.schema.json（の一部）
        "dependencies": {
            "type": {
                "oneOf": [
                    {
                        "properties": {
                            "type": {
                                "enum": ["エレメントさんどーこだ？"]
                            }
                        }
                    },
                    {
                        "properties": {
                            "type": {
                                "enum": ["パートナーをさがせ！"]
                            },
                            "element": {
                                "type": "null"
                            },
                            "place": {
                                "type": "null"
                            }
                        }
                    },
                    {
                        "properties": {
                            "type": {
                                "enum": ["ほんものはどっち？"]
                            },
                            "place": {
                                "type": "null"
                            }
                        }
                    },
                    {
                        "properties": {
                            "type": {
                                "enum": ["てあらい・うがい"]
                            },
                            "animal": {
                                "type": "null"
                            },
                            "element": {
                                "type": "null"
                            },
                            "place": {
                                "type": "null"
                            }
                        }
                    }
                ]
            }
        }
```
</div></details>

ラビリン「`type`に`null`だけを指定すると『この項目は必要ないラビ！』って言えるラビ！」
ニャトラン「ニャルほどな～」

# 1話分のデータを定義する

ラビリン「1話分のデータを定義するラビ！」
ラビリン「最終的には、1話分のデータの配列になるラビ！」

```json:mini_corner.schema.json（の一部）
{
    "type": "array",
    "items": {
        "$id": "#/items",
        "type": "object",
        "required": ["type", "episode_code", "onair_date"],
        "properties": {
            "（省略）"
        },
        "dependencies": {
            "（省略）"
        }
    }
}
```

ペギタン「`"type": "array"`で『配列』にしているペエ」
ラビリン「さっき後回しにした必須の設定は`required`にあるラビ」

# 完成

ラビリン「さらにいろいろな設定を盛り込んで完成ラビ！」

<details><summary>長いので折りたたむラビ</summary><div>

```json:mini_corner.schema.json（2020/05/17版）
{
    "$schema": "http://json-schema.org/draft-07/schema",
    "$id": "https://precure.hokkaidosm.net/healing/data/mini_corner.schema.json",
    "type": "object",
    "title": "ヒープリ　ミニコーナーデータ",
    "description": "エンディングのミニコーナーのデータ",
    "default": {
        "$schema": "https://precure.hokkaidosm.net/healing/data/mini_corner.schema.json",
        "mini_corner": []
    },
    "examples": [
        {
            "$schema": "https://precure.hokkaidosm.net/healing/data/mini_corner.schema.json",
            "mini_corner": [
                {
                    "episode_code": "第1話",
                    "onair_date": "2020-02-02",
                    "type": "エレメントさんどーこだ？",
                    "animal": "ラビリン",
                    "element": "花のエレメントさん",
                    "place": "のどかの家"
                },
                {
                    "episode_code": "第2話",
                    "onair_date": "2020-04-26",
                    "type": "パートナーをさがせ！",
                    "animal": "ペギタン"
                },
                {
                    "episode_code": "第3話",
                    "onair_date": "2020-04-26",
                    "type": "ほんものはどっち？",
                    "animal": "ニャトラン",
                    "element": "光のエレメントさん"
                },
                {
                    "episode_code": "（再）第1話",
                    "onair_date": "2020-04-26",
                    "type": "てあらい・うがい"
                }
            ]
        }
    ],
    "required": [
        "$schema",
        "mini_corner"
    ],
    "additionalProperties": true,
    "properties": {
        "$schema": {
            "$id": "#/properties/$schema",
            "type": "string",
            "format": "uri",
            "title": "スキーマURL",
            "description": "スキーマのURL",
            "default": "https://precure.hokkaidosm.net/healing/data/mini_corner.schema.json",
            "examples": [
                "https://precure.hokkaidosm.net/healing/data/mini_corner.schema.json"
            ]
        },
        "mini_corner": {
            "type": "array",
            "default": [],
            "additionalItems": true,
            "minItems": 0,
            "items": {
                "$id": "#/items",
                "type": "object",
                "required": ["type", "episode_code", "onair_date"],
                "properties": {
                    "episode_code": {
                        "type": "string",
                        "title": "放送回",
                        "description": "放送回　再放送の場合は頭に「（再）」をつける",
                        "examples": [
                            "第1話",
                            "（再）第1話"
                        ]
                    },
                    "onair_date": {
                        "type": "string",
                        "format": "date",
                        "title": "放送日",
                        "examples": [
                            "2020-02-02",
                            "2020-04-26"
                        ]
                    },
                    "type": {
                        "type": "string",
                        "enum": ["エレメントさんどーこだ？", "パートナーをさがせ！", "ほんものはどっち？", "てあらい・うがい"],
                        "title": "ミニコーナー種別"
                    },
                    "animal": {
                        "type": ["string", "null"],
                        "enum": ["ラビリン", "ペギタン", "ニャトラン", null],
                        "title": "担当ヒーリングアニマル",
                        "description": "「てあらい・うがい」の場合は設定しない"
                    },
                    "element": {
                        "type": ["string", "null"],
                        "title": "登場エレメントさん",
                        "description": "「エレメントさんどーこだ？」「ほんものはどっち？」の場合指定",
                        "examples": [
                            "花のエレメントさん",
                            "水のエレメントさん",
                            "光のエレメントさん"
                        ]
                    },
                    "place": {
                        "type": ["string", "null"],
                        "title": "探す場所",
                        "description": "「エレメントさんどーこだ？」の場合指定",
                        "examples": [
                            "旅館沢泉のペットと入れる温泉",
                            "のどかの家"
                        ]
                    }
                },
                "dependencies": {
                    "type": {
                        "oneOf": [
                            {
                                "properties": {
                                    "type": {
                                        "enum": ["エレメントさんどーこだ？"]
                                    }
                                }
                            },
                            {
                                "properties": {
                                    "type": {
                                        "enum": ["パートナーをさがせ！"]
                                    },
                                    "element": {
                                        "type": "null"
                                    },
                                    "place": {
                                        "type": "null"
                                    }
                                }
                            },
                            {
                                "properties": {
                                    "type": {
                                        "enum": ["ほんものはどっち？"]
                                    },
                                    "place": {
                                        "type": "null"
                                    }
                                }
                            },
                            {
                                "properties": {
                                    "type": {
                                        "enum": ["てあらい・うがい"]
                                    },
                                    "animal": {
                                        "type": "null"
                                    },
                                    "element": {
                                        "type": "null"
                                    },
                                    "place": {
                                        "type": "null"
                                    }
                                }
                            }
                        ]
                    }
                }
            }
        }
    }
}
```
</div></details>

ニャトラン「よっしゃー！」
ペギタン「できたペエ！」
ラビリン「今後の状況によっては変わることがあるから[ここ](https://precure.hokkaidosm.net/healing/data/mini_corner.schema.json)から最新版を確認してほしいラビ！」

# JSONデータ例

ラビリン「このJSON Schemaを使って、第1話・第2話・第3話・第11話とおさらいセレクション第1話のデータをJSONで表してみるラビ！」

<details><summary>長いので折りたたむラビ</summary><div>

```json:mini_corner.json
{
    "$schema": "https://precure.hokkaidosm.net/healing/data/mini_corner.schema.json",
    "mini_corner": [
        {
            "episode_code": "第1話",
            "onair_date": "2020-02-02",
            "type": "エレメントさんどーこだ？",
            "animal": "ラビリン",
            "element": "花のエレメントさん",
            "place": "のどかの家"
        },
        {
            "episode_code": "第2話",
            "onair_date": "2020-04-26",
            "type": "パートナーをさがせ！",
            "animal": "ペギタン"
        },
        {
            "episode_code": "第3話",
            "onair_date": "2020-04-26",
            "type": "ほんものはどっち？",
            "animal": "ニャトラン",
            "element": "光のエレメントさん"
        },
        {
            "episode_code": "第11話",
            "onair_date": "2020-04-12",
            "type": "てあらい・うがい"
        },
        {
            "episode_code": "（再）第1話",
            "onair_date": "2020-04-26",
            "type": "てあらい・うがい"
        }
    ]
}
```
</div></details>

ラビリン「ちなみにこのデータの最新版は[ここ](https://precure.hokkaidosm.net/healing/data/mini_corner.json)にあるラビ！毎週[^2]更新される予定ラビ！」
ペギタン「そして[ここ](https://precure.hokkaidosm.net/healing/mini_corner.php)では表で見ることができるペエ」

[^2]: 放送休止の場合を除く

# 便利なサイトの紹介

ラビリン「最後に便利なサイトを3つ紹介するラビ！」

## [Hyperjump - JSON Schema Validator](https://json-schema.hyperjump.io/)

ラビリン「スキーマとJSONを入れてバリデートできるラビ！」
ペギタン「これでチェックしたけど問題なさそうだったペエ」

## [react-jsonschema-form playground](https://rjsf-team.github.io/react-jsonschema-form/)

ニャトラン「ニャ！スキーマとJSONを入れたらフォームができたニャ！」

## [JSON Schema Tool](https://jsonschema.net/home)

ラビリン「JSONを入れたらスキーマが作られるラビ！」