TypeScriptの型がJSONに互換性があることをビルド時に検証する
やりたいこと
以下のような型をJSON.stringify()して永続化してJSON.parse()するような状況があったとする。
code:ts
type User = {
name: string,
age: number,
}
そのときにtype UserにcreatedAt: DateのようなJSON互換性がないメンバを入れることを型のチェックで防ぐ方法が欲しい。DateはJSON.parse(JSON.stringify(user))ができるが.parse()はDateではなくstringにするため非可逆になる問題がある。その他にもFunctionやその他のclassなどJSONに互換性の型はたくさんある。
そこでUserに対してビルド時に有効なアサーションをしたい。そしてJavaScriptにトランスパイルされたらそのアサーションは無になって欲しい。
できたもの
code:ts
type JsonValue =
| undefined
| null
| boolean
| number
| string
| JsonValue[]
type AssertJsonCompatiblity<T> = T extends JsonValue ? { "true": "" } : { "": type ${T & ""} is not JSON compatible. };
// usage 1
type User = {
name: string,
age: number,
children: User[],
}
type _NOT_USED1 = AssertJsonCompatiblity<User>"true"; // usage 2
type UserWithDate = {
name: string,
createdAt: Date,
}
// ERROR: GOOD!
// type _NOT_USED2 = AssertJsonCompatiblity<UserWithDate>"true"; // => Property 'true' does not exist on type '{ "": type ${UserWithDate & ""} is not JSON compatible.; }'.(2339)
もっと直感的な書き方がありそうな気もする。
Dateを誤って入れたときにエラーメッセージに「型名 is not JSON compatible.」が表示されるようになっている。
https://gyazo.com/116a8db008448bc203c12dc8263bf8cf
JavaScriptとして無にならないで直感的な方法なら以下のようなfunctionを作ることがシンプルだと思う。
code:ts
function assertJsonCompatible<T extends JsonValue>() {}
assertJsonCompatible<User>();
// ERROR: GOOD!
// assertJsonCompatible<UserWithDate>();
// => Type 'UserWithDate' does not satisfy the constraint 'JsonValue'.