APIレスポンスに型を通せ
師範 / 目安 35分
凛さんが、fleama API のレスポンスを画面に貼りました。/items を叩くと、こういう形が返ってきます。
プレーンテキスト
{
"status": "ok",
"data": {
"total": 128,
"items": [
{ "id": 1, "name": "深煎りブレンド", "price": 480, "seller": { "id": 7, "name": "ハレノヒ珈琲" } }
]
}
}「いま、これを受け取る変数の型は any です」と凛さん。「res.data.itms と打ち間違えても、誰も止めません。通信の向こう側は、型の空白地帯になっています」
通信の戻りは、確かに外の世界から来ます。しかし「何が返ってくるか分からない」のと「何が返ってくるか書いていない」のは別のことです。API の形は決まっています。決まっているものは書ける。書けば、その形からずれた読み方は全部コンパイラが弾いてくれます。
この形を型にするコツは、外側から内側へ、層ごとに分けることです。1つの巨大な interface に全部詰め込むと、読めないうえに再利用もできません。出品者は Seller、商品1件は Item、data の中身は ItemsData、いちばん外側が ItemsResponse です。Item の中に seller: Seller と書けば、入れ子はそのまま入れ子で表現できます。
凛さんが最後に釘を刺します。「as は使わないでください。as は『コンパイラより自分のほうが正しい』という宣言です。ときには必要ですが、形が分かっているレスポンスに使うのは、ただの手抜きです。型は宣言して通すもので、押し通すものではありません」
完成条件
この問題では通信そのものは行いません。レスポンスを引数で受け取った状態から始めます。
まず、上に貼ったレスポンスの形を4つの interface に分けて定義してください。
Seller—id(number) とname(string)Item—id(number)、name(string)、price(number)、seller(Seller)ItemsData—total(number) とitems(Item[])ItemsResponse—status(string) とdata(ItemsData)
そのうえで pickItems という関数を定義してください。引数は ItemsResponse を1つだけ受け取り、次の形の文字列を返します。
プレーンテキスト
全128件中3件 / 深煎りブレンド(ハレノヒ珈琲) 480円, 抹茶ラテ(和菓子きくや) 580円組み立ての決まりは次のとおりです。
全にtotalの値、件中に 配列に入っている件数 を続け、件で閉じます- 区切りは半角スペースを挟んだ
/です - 商品1件は
商品名(出品者名) 価格円の形にします。商品名と丸括弧のあいだにスペースは入れません - 商品どうしは
,(カンマと半角スペース) でつなぎます itemsが空のときは、うしろ側を該当なしにします。例えば全0件中0件 / 該当なしです
any と as は使わないでください。as を使っていないことは自動判定では測りませんが、凛さんの CI は人の目でも回っています。
進め方
白紙から書いてください。手が止まったらヒントを開けます。ヒントは3段階で、①方針、②使う構文、③部分解の順に出ます。1つも開けずに通せたら、その問題は自分のものです。
開かずに10分粘って進まないなら、①だけ開けてください。粘る時間そのものには価値がありません。価値があるのは、自分で書いた行が動いた瞬間です。
型を書き終えたら、response.data.items[0].seller.name まで補完が効くか確かめてください。エディタが最後まで案内してくれたら、型は通っています。
要件
- interface Seller を定義する。id(number) と name(string)
- interface Item を定義する。id(number)・name(string)・price(number)・seller(Seller)
- interface ItemsData を定義する。total(number) と items(Item[])
- interface ItemsResponse を定義する。status(string) と data(ItemsData)
- pickItems(response: ItemsResponse): string を定義する
- 件数の部分は「全○件中○件」の形。中の数は配列に入っている件数
- 商品1件は「商品名(出品者名) 価格円」の形で、商品どうしは「, 」でつなぐ
- items が空のときはうしろを「該当なし」にする
- any と as を使わないこと
入出力例
pickItems({"data":{"items":[{"id":1,"name":"深煎りブレンド","price":480,"seller":{"id":7,"name":"ハレノヒ珈琲"}},{"id":2,"name":"抹茶ラテ","price":580,"seller":{"id":9,"name":"和菓子きくや"}}],"total":128},"status":"ok"}) → "全128件中2件 / 深煎りブレンド(ハレノヒ珈琲) 480円, 抹茶ラテ(和菓子きくや) 580円"
pickItems({"data":{"items":[{"id":42,"name":"自家製レモネード","price":560,"seller":{"id":7,"name":"ハレノヒ珈琲"}}],"total":1},"status":"ok"}) → "全1件中1件 / 自家製レモネード(ハレノヒ珈琲) 560円"
pickItems({"data":{"items":[],"total":0},"status":"ok"}) → "全0件中0件 / 該当なし"
pickItems({"data":{"items":[{"id":11,"name":"チーズケーキ","price":480,"seller":{"id":3,"name":"洋菓子ミモザ"}},{"id":12,"name":"季節のフルーツティー","price":620,"seller":{"id":7,"name":"ハレノヒ珈琲"}},{"id":13,"name":"カフェオレ","price":520,"seller":{"id":7,"name":"ハレノヒ珈琲"}}],"total":300},"status":"ok"}) → "全300件中3件 / チーズケーキ(洋菓子ミモザ) 480円, 季節のフルーツティー(ハレノヒ珈琲) 620円, カフェオレ(ハレノヒ珈琲) 520円"ヒント
前のヒントを開くと次が開きます。開かずに解けると未開封クリアです