3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

「JSONとの間で変換/復元が可能な値」を表すTypeScript向けの小さな型定義

Last updated at Posted at 2019-01-19

アプリケーションを書いていると、オブジェクトのJSONへの変換や、JSONからのオブジェクトの復元を行うことはよくあります。

ですが、TypeScript上でこうした処理を頻繁に行っていると、「あるオブジェクトや変数の値が、JSONとの間で確実に(一部の情報を失ったりせずに)変換/復元が可能なことを型レベルで保証したい!」と思うことはよくあります。ありますよね?

let food1 = {
    name: 'スクランブルエッグ'
    , calorie: 230
    , stuff: ['', '', '']
}

console.log(food1);
// => { name: 'スクランブルエッグ', calorie: 230, stuff: [ '卵', '油', '塩' ] }
console.log(JSON.parse(JSON.stringify(food1)));
// => { name: 'スクランブルエッグ', calorie: 230, stuff: [ '卵', '油', '塩' ] }
//   JSONに変換した値を、完全に復元できている

let food2 = {
    name: 'スクランブルエッグ'
    , calorie: 230
    , stuff: ['', '', '']
    , created: new Date()
    , updated: undefined
}

console.log(food2);
// => { name: 'スクランブルエッグ', calorie: 230, stuff: ['卵', '油', '塩'], created: 2019-01-19T14:41:04.353Z, updated: undefined }
console.log(JSON.parse(JSON.stringify(food2)));
// => { name: 'スクランブルエッグ', calorie: 230, stuff: ['卵', '油', '塩'], created: '2019-01-19T14:41:04.353Z' }
//   Date型はJSONに変換してしまうと、追加処理なしでは正しく復元できない!
//   また、undefinedもJSONに変換した時点で消滅してしまうため、正しく復元できない!

そこで、「JSONとの間で変換/復元が可能な値」を表すJSONSerializable型の定義を作りました。
たった数行の短い型定義なので、特にnpmパッケージなども作っていませんが、有用に思われた方はコピー&ペーストしてご利用ください。
(配列の型定義方法は、GithubにあるTypeScriptプロジェクトのissuesを参考にさせていただきました)

JSONSerializable.d.ts
/** JSONとの間で、相互に変換/復元が可能な値 */
type JSONSerializable = JSONSerializablePrimitive | JSONSerializableArray | JSONSerializableObject;

type JSONSerializablePrimitive = null | boolean | string | number;

type JSONSerializableObject = { [key: string]: JSONSerializable };

interface JSONSerializableArray extends Array<JSONSerializable> {
}

さっきのサンプルコードに型を適用すると、このように適切にビルドエラーを出力してくれます。

let food1: JSONSerializable = {
    name: 'スクランブルエッグ'
    , calorie: 230
    , stuff: ['', '', '']
}
// => エラーなし

let food2: JSONSerializable = {
    name: 'スクランブルエッグ'
    , calorie: 230
    , stuff: ['', '', '']
    , created: new Date()
    , updated: undefined
}
// => ビルドエラーが発生する 
//   型 '{ name: string; calorie: number; stuff: string[]; created: Date; updated: undefined; }' を
//   型 'JSONSerializable' に割り当てることはできません。(以下略)


// 「変換可能なobjectであること(文字列/数値/真偽値/null/配列ではないこと)」を強制したい場合は
// JSONSerializableObject型を使用する
let food3Obj: JSONSerializableObject;
food3Obj = {name: 'たまごやき'};  // OK
food3Obj = 'たまごやき';          // ビルドエラー (object型ではない)

ただし、この型定義の制限として、循環オブジェクトはエラーとして弾けないことにご注意ください。
(もし循環オブジェクトも弾ける方法があれば教えてください:pray_tone1:

let node1 = {
    parent: null
}
let node2 = {
    child: null
}
node1.parent = node2;
node2.child = node1;

let n1: JSONSerializable = node1; // エラーなし
let n2: JSONSerializable = node2; // エラーなし

console.log(JSON.stringify(n1)); // 循環しているためエラー! (TypeError: Converting circular structure to JSON)

(2020/8/1追記)
なお、場合によっては「オブジェクトのプロパティがundefined値を持つことだけは許可したい」という場合もあると思います。(JSONからの変換/復元を介した時に、値がundefinedであるプロパティが削除されていても問題ない場合)
その場合は、代わりに下記のような定義を使用してください。

JSONSerializable.d.ts
/** JSONとの間で、相互に変換/復元が可能な値 */
type JSONSerializable = JSONSerializablePrimitive | JSONSerializableArray | JSONSerializableObject;

type JSONSerializablePrimitive = null | boolean | string | number;

type JSONSerializableObject = Partial<{ [key: string]: JSONSerializable }>;

interface JSONSerializableArray extends Array<JSONSerializable> {
}
3
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?