使用方法将JSON字符串转换为对象

Cys*_*ack 25 javascript oop

我有一个应用程序,允许用户生成对象,并将它们(在MySQL表中,作为字符串)存储供以后使用.对象可能是:

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

Obj.prototype.setLabel = function(newLabel) {
    this.label = newLabel;
}
Run Code Online (Sandbox Code Playgroud)

如果我在这个对象上使用JSON.stringify,我将只获取有关信息Obj.label(字符串化对象将是一个字符串{label: "new object"}.如果我存储此字符串,并希望以后允许我的用户检索该对象,该setLabel方法将丢失.

所以我的问题是:如何重新实例化对象,以便通过JSON.stringify保存属性,还可以获取应该属于其原型的不同方法.你会怎么做?我在考虑"创建一个空白对象"并"将其与存储的属性合并",但我无法使其工作.

T.J*_*der 20

要做到这一点,你需要在解析JSON字符串时使用"reviver"函数(以及toJSON创建它时构造函数原型上的"replacer"函数或函数).参见第15.12.215.12.3的规范.如果您的环境尚不支持本机JSON解析,您可以使用Crockford的解析器之一(Crockford是JSON的发明者),它也支持"reviver"函数.

这是一个简单的定制示例,适用于符合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);
Run Code Online (Sandbox Code Playgroud)

注意toJSONon Foo.prototype和我们传入的函数JSON.parse.

但问题是,reviver与Foo构造函数紧密耦合.相反,您可以在代码中采用通用框架,其中任何构造函数都可以支持fromJSON(或类似)函数,并且您只能使用一个通用的reviver.

这是一个广义的reviver的例子,它查找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  
Run Code Online (Sandbox Code Playgroud)

为避免在函数toJSONfromJSON函数中重复使用通用逻辑,您可以使用通用版本:

// 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;
}
Run Code Online (Sandbox Code Playgroud)

这里的优点是,您可以按顺序执行特定的"类型"(缺少更好的术语),以便进行序列化和反序列化.所以你可能有一个只使用泛型的"类型":

// `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;
Run Code Online (Sandbox Code Playgroud)

...或者,无论出于何种原因,必须做更多自定义的事情:

// `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;
Run Code Online (Sandbox Code Playgroud)

下面是我们如何测试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 <code>Bar</code> specifically leaves it out.)");
Run Code Online (Sandbox Code Playgroud)


pim*_*vdb 5

您确实可以创建一个空实例,然后将该实例与数据合并.我建议使用库函数以便于使用(如jQuery.extend).

但是你有一些错误(function ... = function(...)和JSON需要键被包围").

http://jsfiddle.net/sc8NU/1/

var data = '{"label": "new object"}';  // JSON
var inst = new Obj;                    // empty instance
jQuery.extend(inst, JSON.parse(data)); // merge
Run Code Online (Sandbox Code Playgroud)

请注意,像这样合并会直接设置属性,所以如果setLabel正在做一些检查,那么就不会这样做.

  • @Cystack&pimvdb:这里的问题是它假设`Obj`*可以*没有参数被调用,并且正如pimvdb所指出的那样,它绕过了该类型的API(可能是好的,或者没有).在我看来,你最好将这些决定推迟到类型本身(当然,如果适当的话,可以愉快地使用`extend`). (2认同)