本稿はJamstackの話ではなくTypeScriptの話になるのですが、PowerCMS XのRESTfulAPIで受け取ったJSONの型定義について考えてみました。Fetch APIでリソースを取得した後response.json()
を実行すると、そのままではunknown型になってしまいます。そこで、記事「Type-safe Data Fetching with unknown in TypeScript | Building SPAs」を参考にtype predicateを書くと、自身で定義した型エイリアス(もしくはnull
)で受け取ることができ、コードのエラーを発見できたりエディタの補完が効いたりするなどの恩恵が得られます。
さて、型定義について考えていきます。検討用に以下のようなカラムを持つモデルを作成しました。
- タイトル(テキスト255)
- 本文(テキスト)
- ファイル(バイナリ)
- 単一ファイルリレーション(数値・アセットモデルへのリレーション)
- 複数ファイルリレーション(リレーション・アセットモデルへのリレーション)
このモデルのオブジェクト1つをRESTful APIで取得すると、長くなるのですが以下のようなJSONが得られます。
{
"id": 1,
"title": "\u8a2d\u8a08\u7528\u30aa\u30d6\u30b8\u30a7\u30af\u30c8",
"text": "\u003Cp\u003E\u672c\u6587\u304c\u5165\u308a\u307e\u3059\u3002\u003C\/p\u003E",
"file": {
"Url": "https:\/\/example.com\/assets\/design_type_definition\/design_type_definition.jpg",
"Label": null,
"Metadata": {
"file_size": 733457,
"image_width": 2048,
"image_height": 1365,
"class": "image",
"extension": "jpg",
"uploaded": "2021-10-19 17:50:18",
"user_id": 1,
"mime_type": "image\/jpeg"
}
},
"single_relation_file": {
"id": 7,
"label": "\u7a7a\u306e\u99c5\u30aa\u30fc\u30c1\u30e3\u30fc\u30c9",
"description": "",
"file": {
"Url": "https:\/\/example.com\/sites1\/assets\/images\/pic_orchard.jpg",
"Label": null,
"Metadata": {
"file_size": 8249438,
"image_width": 2571,
"image_height": 2563,
"class": "image",
"extension": "jpg",
"mime_type": "image\/jpeg",
"uploaded": "2021-09-17 11:00:29",
"user_id": 1
}
},
"extra_path": "assets\/images\/",
"file_name": "pic_orchard.jpg",
"file_ext": "jpg",
"mime_type": "image\/jpeg",
"tags": [
"@diary"
],
"size": 8249438,
"image_width": 2571,
"image_height": 2563,
"class": "image",
"status": 4,
"created_by": 1,
"rev_note": "",
"modified_by": 1,
"rev_diff": "",
"created_on": "2021-09-17 11:03:03",
"modified_on": "2021-09-17 11:03:03",
"workspace_id": 1,
"has_deadline": 0,
"published_on": "2021-09-17 11:00:28",
"unpublished_on": null,
"rev_type": 0,
"rev_object_id": 0,
"rev_changed": "",
"user_id": 1,
"previous_owner": 0,
"uuid": "8ae4aebf-4596-4583-bc54-0692626848b7",
"Permalink": "https:\/\/example.com\/sites1\/assets\/images\/pic_orchard.jpg"
},
"multiple_relation_file": [
{
"id": 6,
"label": "\u4f38\u3073\u3092\u3059\u308b\u91ce\u826f\u732b",
"description": "",
"file": {
"Url": "https:\/\/example.com\/sites1\/assets\/images\/pic_cat.jpg",
"Label": null,
"Metadata": {
"file_size": 7070101,
"image_width": 2352,
"image_height": 2352,
"class": "image",
"extension": "jpg",
"mime_type": "image\/jpeg",
"uploaded": "2021-09-17 11:00:28",
"user_id": 1
}
},
"extra_path": "assets\/images\/",
"file_name": "pic_cat.jpg",
"file_ext": "jpg",
"mime_type": "image\/jpeg",
"tags": [
"@diary"
],
"size": 7070101,
"image_width": 2352,
"image_height": 2352,
"class": "image",
"status": 4,
"created_by": 1,
"rev_note": "",
"modified_by": 1,
"rev_diff": "",
"created_on": "2021-09-17 11:02:29",
"modified_on": "2021-09-17 11:02:29",
"workspace_id": 1,
"has_deadline": 0,
"published_on": "2021-09-17 11:00:28",
"unpublished_on": null,
"rev_type": 0,
"rev_object_id": 0,
"rev_changed": "",
"user_id": 1,
"previous_owner": 0,
"uuid": "f152c7b3-04fc-4481-8a31-0ec5ad070c61",
"Permalink": "https:\/\/example.com\/sites1\/assets\/images\/pic_cat.jpg"
},
{
"id": 5,
"label": "\u30b0\u30ea\u30fc\u30f3\u30e9\u30a4\u30f3\u304b\u3089\u5185\u6d77\u5927\u6a4b\u304c\u898b\u3048\u305f",
"description": "",
"file": {
"Url": "https:\/\/example.com\/sites1\/assets\/images\/pic_utsumi.jpg",
"Label": null,
"Metadata": {
"file_size": 4091734,
"image_width": 2675,
"image_height": 2675,
"class": "image",
"extension": "jpg",
"mime_type": "image\/jpeg",
"uploaded": "2021-09-17 11:00:28",
"user_id": 1
}
},
"extra_path": "assets\/images\/",
"file_name": "pic_utsumi.jpg",
"file_ext": "jpg",
"mime_type": "image\/jpeg",
"tags": [
"@diary"
],
"size": 4091734,
"image_width": 2675,
"image_height": 2675,
"class": "image",
"status": 4,
"created_by": 1,
"rev_note": "",
"modified_by": 1,
"rev_diff": "",
"created_on": "2021-09-17 11:02:04",
"modified_on": "2021-09-17 11:02:04",
"workspace_id": 1,
"has_deadline": 0,
"published_on": "2021-09-17 11:00:28",
"unpublished_on": null,
"rev_type": 0,
"rev_object_id": 0,
"rev_changed": "",
"user_id": 1,
"previous_owner": 0,
"uuid": "74889c2f-d66b-4072-b186-7219dde98119",
"Permalink": "https:\/\/example.com\/sites1\/assets\/images\/pic_utsumi.jpg"
}
]
}
型定義を書いてみる
Sample型エイリアスとして書き始めます。type
なのかinterface
なのかはまだ研究中ですが、ひとまずtype
にしておきます。IDはnumber
、タイトル・本文はstring
と特に迷うことはなさそうです。
type Sample = {
id: number,
title: string,
text: string
};
バイナリタイプのカラムは項目が多いですが地道に型を当てはめるだけかと思います。Binary
に書き出してみます。Url
やPermalink
がない場合があるので注意が必要です。
type Binary = {
Url?: string,
Label: string | null,
Metadata: {
file_size: number,
image_width?: number,
image_height?: number,
class: string,
extension: string,
uploaded: string,
user_id: number,
mime_type: string
}
};
数値タイプで単一ファイルのリレーション(アセットモデルへのリレーション)の場合も基本的には地道に型を当てはめていくのですが、アセットモデルのファイルカラムはバイナリタイプなので先程のBinary
が使い回せました。
type RelationAsset = {
id: number,
label: string,
description: string,
file: Binary,
extra_path: string,
file_name: string,
file_ext: string,
mime_type: string,
tags: string[],
size: number
image_width: number
image_height: number
class: string,
status: number
created_by: number
rev_note: string,
modified_by: number
rev_diff: string,
created_on: string,
modified_on: string,
workspace_id: number
has_deadline: number
published_on: string,
unpublished_on: string | null,
rev_type: number
rev_object_id: number
rev_changed: string,
user_id: number
previous_owner: number
uuid: string,
Permalink?: string
};
リレーションタイプで複数ファイルのリレーション(アセットモデルへのリレーション)の場合は、単一ファイルのリレーションの内容が配列になっていましたので、RelationAsset[]
です。
よって、Sample型エイリアスは以下のようになりました。
type Sample = {
id: number,
title: string,
text: string,
file: Binary,
single_relation_file: RelationAsset,
multiple_relation_file: RelationAsset[]
};
今後の課題
PowerCMS Xの特徴として自由にモデル・カラムが定義できるので、この型定義を使えば大丈夫というファイルをお渡しすることができません。リソースを取得する時のパラメータでカラムを限定するケースもあります。ただ、カラムタイプは限られていますし、先程紹介したバイナリタイプのカラムのように規則性もありそうです。地道に調査した後に自動生成を検討しAPIのエンドポイントを介して型定義を取得できないだろうか?(もしくはモデルの編集画面からエクスポートする)と考えています。json-schema-to-typescript等のツールについても調べてみようと思います。