メソッドでJSON文字列をオブジェクトに変換する

私は、ユーザーがオブジェクトを生成し、後で使用するためにそれらを(文字列としてのMySQLテーブルに)格納することができるアプリケーションを持っています。オブジェクトは次のようになります。

function Obj() {
    this.label = "new object";
}

Obj.prototype.setLabel = function(newLabel) {
    this.label = newLabel;
}

このオブジェクトでJSON.stringifyを使用すると、 Obj.label
の情報のみが取得されます(文字列化されたオブジェクトは {label: "new object"}
。この文字列を保存しておいて、後でオブジェクトを取得できるようにするには、 setLabel
メソッドが失われます。

ですから、私の質問は、オブジェクトを再インスタンス化して、JSON.stringifyのおかげで保存されたプロパティを保持するだけでなく、そのプロトタイプに属するべきさまざまなメソッドを取得する方法です。あなたはどうしますか?私は
“空のオブジェクトを作成する”と
“保存されたもののプロパティとマージする”に沿って何かを考えていましたが、動作させることはできません。

ベストアンサー

これを行うには、JSON文字列(およびコンストラクタのプロトタイプを作成する際に、 “replacer”関数または
toJSON 関数を解析するときに “reviver”関数を使用する必要があります)。 セクション15.12.2
15.12.3
を参照してください。ご使用の環境がまだネイティブJSON解析をサポートしていない場合は、 Crockfordのパーサーの1つを使用できます(CrockfordはJSON)もサポートしています。

ここでは、ES5準拠のブラウザ(またはES5の動作をエミュレートするライブラリ)(ライブコピー、ChromeまたはFirefoxで動作するライブラリ、または類似)、より一般化されたソリューションの例を見てください。

// Our constructor function
function Foo(val) {
  this.value = val;
}
Foo.prototype.nifty = "I'm the nifty inherited property.";
Foo.prototype.toJSON = function() {
  return "/Foo(" + this.value + ")/";
};

// An object with a property, `foo`, referencing an instance
// created by that constructor function, and another `bar`
// which is just a string
var obj = {
  foo: new Foo(42),
  bar: "I'm bar"
};

// Use it
display("obj.foo.value = " + obj.foo.value);
display("obj.foo.nifty = " + obj.foo.nifty);
display("obj.bar = " + obj.bar);

// Stringify it with a replacer:
var str = JSON.stringify(obj);

// Show that
display("The string: " + str);

// Re-create it with use of a "reviver" function
var obj2 = JSON.parse(str, function(key, value) {
  if (typeof value === "string" &&
      value.substring(0, 5) === "/Foo(" &&
      value.substr(-2) == ")/"
     ) {
    return new Foo(value.substring(5, value.length - 2));
  }
  return value;
});

// Use the result
display("obj2.foo.value = " + obj2.foo.value);
display("obj2.foo.nifty = " + obj2.foo.nifty);
display("obj2.bar = " + obj2.bar);

Foo.prototypetoJSON と、
JSON.parse に渡す関数に注目してください。

しかし、そこでの問題は、復活祭が Foo
コンストラクタに緊密に結合されていることです。代わりに、コンストラクタ関数が fromJSON
(または類似の)関数をサポートできる汎用フレームワークをコード内に採用することができます。

次に、 ctor プロパティと data
プロパティを検索し、見つかった場合は ctor.fromJSON
を呼び出し、受信した完全な値(実例):

// A generic "smart reviver" function.
// Looks for object values with a `ctor` property and
// a `data` property. If it finds them, and finds a matching
// constructor that has a `fromJSON` property on it, it hands
// off to that `fromJSON` fuunction, passing in the value.
function Reviver(key, value) {
  var ctor;

  if (typeof value === "object" &&
      typeof value.ctor === "string" &&
      typeof value.data !== "undefined") {
    ctor = Reviver.constructors[value.ctor] || window[value.ctor];
    if (typeof ctor === "function" &&
        typeof ctor.fromJSON === "function") {
      return ctor.fromJSON(value);
    }
  }
  return value;
}
Reviver.constructors = {};//A list of constructors the smart reviver should know about  

toJSON 関数と fromJSON
関数で共通ロジックを繰り返さないようにするには、汎用バージョンを使用できます。

// A generic "toJSON" function that creates the data expected
// by Reviver.
// `ctorName`  The name of the constructor to use to revive it
// `obj`       The object being serialized
// `keys`      (Optional) Array of the properties to serialize,
//             if not given then all of the objects "own" properties
//             that don't have function values will be serialized.
//             (Note: If you list a property in `keys`, it will be serialized
//             regardless of whether it's an "own" property.)
// Returns:    The structure (which will then be turned into a string
//             as part of the JSON.stringify algorithm)
function Generic_toJSON(ctorName, obj, keys) {
  var data, index, key;

  if (!keys) {
    keys = Object.keys(obj);//Only "own" properties are included
  }

  data = {};
  for (index = 0; index < keys.length; ++index) {
    key = keys[index];
    data[key] = obj[key];
  }
  return {ctor: ctorName, data: data};
}

// A generic "fromJSON" function for use with Reviver: Just calls the
// constructor function with no arguments, then applies all of the
// key/value pairs from the raw data to the instance. Only useful for
// constructors that can be reasonably called without arguments!
// `ctor`      The constructor to call
// `data`      The data to apply
// Returns:    The object
function Generic_fromJSON(ctor, data) {
  var obj, name;

  obj = new ctor();
  for (name in data) {
    obj[name] = data[name];
  }
  return obj;
}

ここでの利点は、シリアライズとデシリアライズの方法について、特定の「型」(より良い用語がないため)の実装を延期することです。だから、ジェネリックを使うだけの
“タイプ”があるかもしれません:

// `Foo` is a constructor function that integrates with Reviver
// but doesn't need anything but the generic handling.
function Foo() {
}
Foo.prototype.nifty = "I'm the nifty inherited property.";
Foo.prototype.spiffy = "I'm the spiffy inherited property.";
Foo.prototype.toJSON = function() {
  return Generic_toJSON("Foo", this);
};
Foo.fromJSON = function(value) {
  return Generic_fromJSON(Foo, value.data);
};
Reviver.constructors.Foo = Foo;

…または何らかの理由で何かもっと慣習をしなければならないもの:

// `Bar` is a constructor function that integrates with Reviver
// but has its own custom JSON handling for whatever reason.
function Bar(value, count) {
  this.value = value;
  this.count = count;
}
Bar.prototype.nifty = "I'm the nifty inherited property.";
Bar.prototype.spiffy = "I'm the spiffy inherited property.";
Bar.prototype.toJSON = function() {
 //Bar's custom handling *only* serializes the `value` property
 //and the `spiffy` or `nifty` props if necessary.
  var rv = {
    ctor: "Bar",
    data: {
      value: this.value,
      count: this.count
    }
  };
  if (this.hasOwnProperty("nifty")) {
    rv.data.nifty = this.nifty;
  }
  if (this.hasOwnProperty("spiffy")) {
    rv.data.spiffy = this.spiffy;
  }
  return rv;
};
Bar.fromJSON = function(value) {
 //Again custom handling, for whatever reason Bar doesn't
 //want to serialize/deserialize properties it doesn't know
 //about.
  var d = value.data;
      b = new Bar(d.value, d.count);
  if (d.spiffy) {
    b.spiffy = d.spiffy;
  }
  if (d.nifty) {
    b.nifty = d.nifty;
  }
  return b;
};
Reviver.constructors.Bar = Bar;

FooBar
が期待どおりに動作することをテストする方法は次のとおりです(ライブコピー ):

// An object with `foo` and `bar` properties:
var before = {
  foo: new Foo(),
  bar: new Bar("testing", 42)
};
before.foo.custom = "I'm a custom property";
before.foo.nifty = "Updated nifty";
before.bar.custom = "I'm a custom property";//Won't get serialized!
before.bar.spiffy = "Updated spiffy";

// Use it
display("before.foo.nifty = " + before.foo.nifty);
display("before.foo.spiffy = " + before.foo.spiffy);
display("before.foo.custom = " + before.foo.custom + " (" + typeof before.foo.custom + ")");
display("before.bar.value = " + before.bar.value + " (" + typeof before.bar.value + ")");
display("before.bar.count = " + before.bar.count + " (" + typeof before.bar.count + ")");
display("before.bar.nifty = " + before.bar.nifty);
display("before.bar.spiffy = " + before.bar.spiffy);
display("before.bar.custom = " + before.bar.custom + " (" + typeof before.bar.custom + ")");

// Stringify it with a replacer:
var str = JSON.stringify(before);

// Show that
display("The string: " + str);

// Re-create it with use of a "reviver" function
var after = JSON.parse(str, Reviver);

// Use the result
display("after.foo.nifty = " + after.foo.nifty);
display("after.foo.spiffy = " + after.foo.spiffy);
display("after.foo.custom = " + after.foo.custom + " (" + typeof after.foo.custom + ")");
display("after.bar.value = " + after.bar.value + " (" + typeof after.bar.value + ")");
display("after.bar.count = " + after.bar.count + " (" + typeof after.bar.count + ")");
display("after.bar.nifty = " + after.bar.nifty);
display("after.bar.spiffy = " + after.bar.spiffy);
display("after.bar.custom = " + after.bar.custom + " (" + typeof after.bar.custom + ")");

display("(Note that after.bar.custom is undefined because Bar specifically leaves it out.)");

コメントする

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です