

サーバーとJSONを用いて通信するWebアプリケーションをTypescriptで書いています．JSONにシリアライズ/デシリアライズするとき，JSONにメソッドが含まれないので，デシリアライズしたデータを使ってオブジェクトを作ろうと考えたのですが，いい方法が分かりません．

今は次のようなコードを書いています．

[code]

    class Pen{
        constructor(public color: String,public size:number) { }
        draw(): void {
            //do something
        }
    }
    var json = JSON.stringify(new Pen("rgb(0,0,0)",10));
    var pen_data = <Pen> JSON.parse(json);
    //pen_data.draw(); //TypeError: pen_data.draw is not a function
    
    //pen_dataからPenのオブジェクトを作りたい
    var new_pen = new Pen(pen_data.color, pen_data.size);
    
[/code]

ですが，このコードはPenのプロパティが増えたときに一々書き直す必要がありますし，他のクラスでも同じことがしたくなったら同じようなコードを書かなければいけません．

このコードをジェネリックに書くにはどうすればよいでしょうか．


CC BY-SA 3.0
2015-06-27T10:54:26.793
11799
2015-06-28T17:36:06.653
8573
question
javascript json typescript
デシリアライズしたプロパティからオブジェクトをつくりたい


直接的な方法はないと思います。オブジェクト指向の「オブジェクト」はデータと操作をひとまとめにして扱うものであるのに対して、シリアライズはオブジェクトの「データ」の部分のみを保存したり復元するものであり、原理的にオブジェクト指向の「オブジェクト」はデータの永続化と相性の悪いものだからです。

幾つか解決策は考えられます。

# 1. 我慢してそれぞれのオブジェクトの内容にあわせていちいちプロパティを復元する方法

質問者さんが提示したサンプルコードの方法です。Penオブジェクトと対応するJSON形式のデータをまったく別々に扱います。デシリアライズの時にいちいちコンストラクタを呼ばなければなりませんし、プロパティが増減したらその都度デシリアライズの部分も修正します。とにかく手間がかかり面倒ですし、Penオブジェクトとそれに対応するJSONデータが存在することでデータ構造としても重複しているなどあまりに冗長ですが、オブジェクト指向プログラミングではある意味「真っ当」な方法ではあります。

# 2. データと操作を分離する方法

クラスを使うのを諦め、`draw`はPenクラスのメソッドではなく、独立した関数として定義します。`Pen`はクラスではなくインターフェイスになり、penの実体はメソッドを持たない単なるオブジェクトになります。

[code]

    function draw(pen: Pen){
        ....
    }
    
    interface Pen { 
        color: String; 
        size: Number; 
    }
    
    var blackPen: Pen = { color: "rgb(0,0,0)", size: 10 };
    
    draw(blackPen);
    
    var json = JSON.stringify(blackPen);
    var pen = <Pen> JSON.parse(json);
    
    draw(pen);
    
[/code]

単純でわかりやすい方法だと思いますし、データ構造がもっと複雑になっても、`stringify/parse`一発でシリアライズ/デシリアライズできるという利点もあります。たとえば、

[code]

    interface PaintTool {
        penList: [Pen];
        canvasWidth: Number;
        canvasHeight: Number;
        filePath: String;
    }
    
[/code]

のような、内部に`Pen`を持つような更に大きなデータ構造でも、内部にメソッドを持たないオブジェクトであればデータ構造全体を一発でシリアライズ/デシリアライズできます。ただし、クラスやプロトタイプのようなオブジェクト指向プログラミングらしい機能をほとんど使えないという欠点があります。`Pen`がサブクラスを持っていて`draw`がオーバーライドされるようなときには使えません。

# 3. シリアライズ用のライブラリを使う方法

[resurrect-js](https://github.com/skeeto/resurrect-
js)のようなライブラリを使う方法があります。幾らか制限があって、どんなオブジェクトでもシリアライズできるというわけではありませんし、TypeScriptの「クラス」に対して使用可能なのかどうか確認していませんが、おそらく質問者さんがイメージしている方法に近い方法だと思います。

Readmeによれば、resurrect-jsでは次のように書けるようです。

[code]

    function Foo() {}
    Foo.prototype.greet = function() { return "hello"; };
    
    // Behavior is preserved:
    var necromancer = new Resurrect();
    var json = necromancer.stringify(new Foo());
    var foo = necromancer.resurrect(json);
    foo.greet();  // => "hello"
    
[/code]

`JSON.strongify`の代わりに`Resurrect#stringify`メソッドを、`JSON.parse`の代わりに`Resurrect#resurrect`を呼ぶなど、使いかたとしても直感的だと思います。メソッドも条件付きでシリアライズ可能なようです。用途によりますが、検討する価値はありそうです。

# 4. メタプログラミングで無理やり復元する方法

JavaScriptには極めて柔軟なメタプログラミングの方法が提供されており、`__proto__`を直接いじることができますし、`Object.keys`でオブジェクトが保持しているプロパティの名前を動的に調べることもできます。`constructor`プロパティでコンストラクタを調べることもできますし、何らかの規約を定めてオブジェクトのプロトタイプを文字列の形でJSONデータに格納しておき、デシリアライズ時には動的にそのコンストラクタを探してきて呼び出し、プロトタイプオブジェクトを設定し、プロパティを設定して復元するような方法は不可能ではないと思います(
参考: [dynamic object construction in
javascript?](https://stackoverflow.com/questions/3871731/dynamic-object-
construction-in-javascript) )。

Javaなどではリフレクションを駆使してそのようなシリアライゼーションを行う機能があります。Javaでオブジェクトをシリアライズ可能にするには引数のないコンストラクタの定義が必要なのは、そのような方法で動的にデシリアライズするためです。3で挙げたresurrect-
jsでやっているのもそういった方法です。ただし、そのような方法が自分の用途で使えるとは限らず、例えば質問者さんはTypeScriptを使っているので、JavaScript向けに作られているresurrect-
jsが流用できるとは限りません。resurrect-jsのようなライブラリが使えないなら、同様の機構を自分の用途に合わせて独自に実装する方法もあります。

メタプログラミングを駆使してそのような機構を独自に実装すれば、プロパティが増減してもそれに合わせて自動でシリアライズ/デシリアライズするようなことを実現するのも不可能ではないでしょうが、そのようなメタプログラミングは複雑でバグが紛れ込みやすく、決して扱いやすい方法とは言えないでしょう。TypeScriptの静的型付けも役に立ちません。決して簡単ではないので、メタプログラミングは最終手段だと思ったほうがいいと思います。

* * *

私なら2を選びます。オブジェクト指向プログラミングに拘泥しないのであれば、もっとも単純でわかりやすい方法だと思うからです。この場面では、オブジェクト指向のクラスやプロトタイプが使えなくて不便だとも思いません。このシリアライズの問題は、データと操作をひとまとめにしてしまうオブジェクト指向プログラミングの欠点を示すわかりやすい例だと思います。`color`や`size`のような永続化可能なデータと、`draw`メソッドという永続化不可能な操作を、分離せずに`Pen`というオブジェクトしてまとめて永続化や復元しようとしているのが問題を引き起こした原因であり、データと操作を分離してしまえば問題は自然に解決します。


CC BY-SA 3.0
2015-06-28T15:57:52.877
11832
2015-06-28T17:36:06.653
2017-05-23T12:38:56.083
-1
10330
11799
answer