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

时间:2011-11-13 12:15:22

标签: javascript oop

我有一个应用程序,允许用户生成对象,并将它们(在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保存属性,同时还可以获取应该属于其原型的不同方法。你会怎么做?我正在考虑“创建一个空白对象”并“将其与存储的属性合并”,但我无法使其工作。

8 个答案:

答案 0 :(得分:20)

要做到这一点,你需要在解析JSON字符串时使用“reviver”函数(在构造函数的原型上创建它时使用“replacer”函数或toJSON函数)。请参阅规范的Section 15.12.215.12.3。如果您的环境尚不支持本机JSON解析,您可以使用one of Crockford's parsers(Crockford是JSON的发明者),它也支持“reviver”函数。

这是一个简单的定制示例,适用于符合ES5标准的浏览器(或模拟ES5行为的库)(live copy,在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);

请注意toJSON上的Foo.prototype以及我们传递给JSON.parse的函数。

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

这是一个广义的reviver的例子,它查找ctor属性和data属性,如果找到则调用ctor.fromJSON,传入它收到的全部值({{3 }}):

// 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  

为了避免在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;
}

这里的优势在于,您可以根据序列化和反序列化的特定“类型”(缺少更好的术语)来实现。所以你可能有一个只使用泛型的“类型”:

// `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按预期工作的方式(live example):

// 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.)");

答案 1 :(得分: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

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

答案 2 :(得分:1)

据我所知,这意味着要远离JSON;你现在正在定制它,所以你要承担所有潜在的麻烦。 JSON的想法是仅包含数据,而不是代码,以避免在允许包含代码时获得的所有安全问题。允许代码意味着您必须使用eval来运行该代码并且eval是邪恶的。

答案 3 :(得分:1)

如果你想使用Obj的设定者:

Obj.createFromJSON = function(json){
   if(typeof json === "string") // if json is a string
      json = JSON.parse(json); // we convert it to an object
   var obj = new Obj(), setter; // we declare the object we will return
   for(var key in json){ // for all properties
      setter = "set"+key[0].toUpperCase()+key.substr(1); // we get the name of the setter for that property (e.g. : key=property => setter=setProperty
      // following the OP's comment, we check if the setter exists :
      if(setter in obj){
         obj[setter](json[key]); // we call the setter
      }
      else{ // if not, we set it directly
         obj[key] = json[key];
      }
   }
   return obj; // we finally return the instance
};

这要求您的类具有其所有属性的setter。 这个方法是静态的,所以你可以像这样使用:

var instance = Obj.createFromJSON({"label":"MyLabel"});
var instance2 = Obj.createFromJSON('{"label":"MyLabel"}');

答案 4 :(得分:0)

尝试在方法上使用toString。

更新

迭代obj中的方法并将它们存储为字符串,然后使用新的Function实例化它们。

storedFunc = Obj.prototype.setLabel.toString();
Obj2.prototype['setLabel'] = new Function("return (" + storedFunc + ")")();

答案 5 :(得分:0)

您必须编写自己的stringify方法,通过使用toString方法将函数转换为字符串来将函数存储为属性。

答案 6 :(得分:0)

JavaScript是prototype based programming language,它是无类语言,通过克隆作为原型的现有对象的过程实现面向对象。

序列化JSON会考虑任何方法,例如,如果你有一个对象

var x = {
    a: 4
    getText: function() {
       return x.a;
    }
};

只有{ a:4 }才能获得序列化程序跳过getText方法。

一年前我遇到了同样的麻烦,我必须为每个域对象维护一个单独的帮助器类,并在需要时将$.extend()用于我的反序列化对象,只需更像是将方法用于域对象的基类

答案 7 :(得分:0)

从ECMAScript 6开始,您可以执行以下操作:

Object.assign(new Obj(), JSON.parse(rawJsonString))

注意:首先创建一个已定义类型的新空对象,然后使用解析的JSON覆盖其属性。并非如此。

这些方法定义行为,不包含任何变量数据。它们被“存储”为您的代码。因此,您实际上不必将它们存储在数据库中。