【问题标题】:Turning JSON strings into objects with methods使用方法将 JSON 字符串转换为对象
【发布时间】:2011-12-28 00:39:42
【问题描述】:

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

【问题讨论】:

  • 我为你鼓掌@Cystack!转换 Javascript 对象(纯数据属性,没有方法)的概念在这里讨论了很多。然而,这是我遇到的第一个真正关注对象的问题,即具有方法的对象。 JSON.stringify 方法通常存在足够多的陷阱(循环引用、转义/字符集等),因此大多数问题都集中在这一点上,遗憾的是我几乎找不到关于这种“自动重新实例化”的信息。

标签: javascript oop


【解决方案1】:

尝试在方法上使用 toString。

更新:

遍历obj中的方法并将它们存储为字符串,然后用new Function实例化它们。

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

【讨论】:

  • 你是什么意思?以便我将方法作为字符串存储在对象中?这违背了原型的目的,不是吗?
  • 不一定在对象中,在您的数据库中,只需为方法及其所属的对象添加一个表,然后为对象发送回普通 json,然后将方法字符串添加到对象中。 .
  • 既然 OP 询问对象是否有方法,当谈到 JSON 时,我认为使用 replacer 将方法存储在字符串中的想法(可能应该转义和或 base64 不制动 JSON 表示法)并在以后恢复这些方法。该建议可能不会被考虑(很好,即将方法存储在数据库中)但我认为它链接到 OP 并且答案明确地丰富了解决方案空间,@j-a 谢谢!
【解决方案2】:

据我所知,这意味着远离 JSON;您现在正在对其进行自定义,因此您承担了所有潜在的麻烦。 JSON 的想法是只包含数据,而不是代码,以避免在允许包含代码时遇到的所有安全问题。允许代码意味着您必须使用 eval 来运行该代码,而 eval 是邪恶的。

【讨论】:

  • 我不想将代码放在 JSON 中,我只是提到它不以任何方式存储方法的事实。但我愿意接受任何解决方案!
  • “据我所知,这意味着远离 JSON...” 并非如此,reviver 函数已经存在很长时间了。
  • 我想我不明白这个问题的本质。 reviver 函数允许您使用现有函数(不是 JSON 中的函数)在读取数据时对数据执行操作,包括构建对象和添加您自己的代码。这可能意味着您可以存储属性名称,以标记应将哪些函数添加到重构对象。但它不会将代码存储在 JSON 中。除非我遗漏了什么……如果我遗漏了,请随时赐教。
【解决方案3】:

您确实可以创建一个空实例,然后将该实例与数据合并。我建议使用库函数以方便使用(如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 正在做一些检查,则不会这样做。

【讨论】:

  • 非常好!这是最接近我的想法的。现在唯一的缺点是由于各种原因我花了几个小时不包含 jQuery,我讨厌自己仅仅为此包含它 ^^ 我会查看 .extend 函数的代码
  • @Cystack:许多库中有许多extend 函数;您也可以只借一个并将其复制到您的项目中。 underscore.js 一个没有依赖关系:documentcloud.github.com/underscore/underscore.js.
  • @Cystack & pimvdb:这里的问题是它假设Obj 可以 不带参数调用,正如 pimvdb 指出的那样,它绕过了类型(可能很好,也可能不是)。在我看来,最好将这些决定推迟到类型本身(当然,如果合适的话,可以愉快地使用extend)。
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    为此,您需要在解析 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);
    

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

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

    这是一个通用的 reviver 示例,它查找 ctor 属性和 data 属性,如果找到,则调用 ctor.fromJSON,并传入它收到的完整值 (live example):

    // 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 copy):

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

    【讨论】:

    • 有趣的建议,但这仅适用于您的 Foo “类”。如果您查看我的 Obj(),我无法使用它来恢复对象,因为 .labelproperty 将丢失。
    • 根据您的示例查看此内容:jsfiddle.net/cBBd4 漂亮的属性丢失了
    • @Cystack:实际上,我添加了一个通用的 reviver(只是用更好的 reviver 替换了它)。回复你的小提琴:当然,这取决于 Foo 对象的 toJSON 函数,它需要确保保存适当的属性(许多对象将具有不应序列化的属性)。你也可以做一个通用的事情,如我最近的更新所示。
    • 我很感谢展示的想法。重新实例化一个对象,以便序列化过程在具有方法的对象中再次结束的概念是有用的。除了循环引用和 JSON.stringify 的经典问题之外,还有必要存储 真实对象(=with methods)(不仅仅是数据)。无论如何,必须手动引用对象名称似乎还不是最完美的解决方案。 this 自动获取构造函数引用呢?
    【解决方案6】:

    如果你想使用 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
    };
    

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

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

    【讨论】:

    • +1 不错的解决方案;也许如果setXXX 不可用,直接设置它(因为相当多的属性可能不需要单独的setter 函数)。
    【解决方案7】:

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

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

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

    您只会得到{ a:4 },其中getText 方法被序列化程序跳过。

    一年前我遇到了同样的麻烦,我不得不为我的每个域对象维护一个单独的帮助类,并在需要时将 $.extend() 用于我的 反序列化 对象,就像拥有领域对象基类的方法

    【讨论】:

      【解决方案8】:

      从 ECMAScript 6 开始,您可以这样做:

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-08-02
        • 1970-01-01
        • 1970-01-01
        • 2019-08-27
        • 2020-05-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多