为了实现这个目标,您需要在解析JSON字符串时使用“reviver”函数(并在创建它时使用“replacer”函数或构造函数原型上的
toJSON
函数)。请参见规范的
Section 15.12.2和
15.12.3。如果您的环境尚不支持本机JSON解析,则可以使用
Crockford的其中一种解析器(Crockford是JSON的发明者),这些解析器还支持“reviver”函数。
这是一个简单的定制示例,适用于符合ES5标准的浏览器(或模拟ES5行为的库)(
live copy,在Chrome或Firefox或类似浏览器中运行),但请查看示例以获取更通用的解决方案。
function Foo(val) {
this.value = val;
}
Foo.prototype.nifty = "I'm the nifty inherited property.";
Foo.prototype.toJSON = function() {
return "/Foo(" + this.value + ")/";
};
var obj = {
foo: new Foo(42),
bar: "I'm bar"
};
display("obj.foo.value = " + obj.foo.value);
display("obj.foo.nifty = " + obj.foo.nifty);
display("obj.bar = " + obj.bar);
var str = JSON.stringify(obj);
display("The string: " + str);
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;
});
display("obj2.foo.value = " + obj2.foo.value);
display("obj2.foo.nifty = " + obj2.foo.nifty);
display("obj2.bar = " + obj2.bar);
请注意
Foo.prototype
上的
toJSON
和我们传递给
JSON.parse
的函数。
问题在于复活者与
Foo
构造函数紧密耦合。 您可以在代码中采用通用框架,其中任何构造函数都可以支持
fromJSON
(或类似)函数,并且您可以使用一个通用的复苏者。
下面是一个通用的复苏者示例,它查找
ctor
属性和
data
属性,如果找到,则调用
ctor.fromJSON
,并将其接收到的完整值传递给它。
实时示例:
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 = {};
为了避免在
toJSON
和
fromJSON
函数中重复编写通用逻辑,你可以创建通用版本:
function Generic_toJSON(ctorName, obj, keys) {
var data, index, key;
if (!keys) {
keys = Object.keys(obj);
}
data = {};
for (index = 0; index < keys.length; ++index) {
key = keys[index];
data[key] = obj[key];
}
return {ctor: ctorName, data: data};
}
function Generic_fromJSON(ctor, data) {
var obj, name;
obj = new ctor();
for (name in data) {
obj[name] = data[name];
}
return obj;
}
这里的优势在于您将序列化和反序列化的实现交给了特定“类型”(暂时无更好的术语)来处理。因此,您可以使用只使用泛型的“类型”。
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;
...或者出于某种原因需要执行更多自定义操作的应用程序:
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() {
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) {
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;
以下是我们如何测试Foo
和Bar
是否按预期工作的方法(实时示例):
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";
before.bar.spiffy = "Updated spiffy";
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 + ")");
var str = JSON.stringify(before);
display("The string: " + str);
var after = JSON.parse(str, Reviver);
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 <code>Bar</code> specifically leaves it out.)");
JSON.stringify
方法通常存在足够多的陷阱(循环引用、转义/字符集等),因此大多数问题都集中在此方面,可惜很少有关于这种“自动重新实例化”的内容。 - humanityANDpeace