【问题标题】:Best way to serialize/unserialize objects in JavaScript?在 JavaScript 中序列化/反序列化对象的最佳方法?
【发布时间】:2011-09-23 04:21:13
【问题描述】:

我的应用程序中有许多 JavaScript 对象,例如:

function Person(age) {
    this.age = age;
    this.isOld = function (){
        return this.age > 60;
    }
}
// before serialize, ok
var p1 = new Person(77);
alert("Is old: " + p1.isOld());

// after, got error Object #<Object> has no method 'isOld'
var serialize = JSON.stringify(p1);
var _p1 = JSON.parse(serialize);
alert("Is old: " + _p1.isOld());

JS Fiddle

我的问题是:是否有最佳实践/模式/提示以与序列化之前相同的类型恢复我的对象(在这种情况下是 Person 类的实例)?

我的要求:

  • 优化磁盘使用:我在内存中有一个很大的对象树。所以,我不想存储函数。
  • 解决方案可以使用 jQuery 和其他库来序列化/反序列化。

【问题讨论】:

  • 您需要提供一些可以确定“最佳”的标准,例如最快、最灵活、最强大。序列化对象的唯一方法是遍历其属性并创建属性及其值的文本表示。是使用一些内置函数(例如JSON.stringify)还是自己编写取决于您是否有JSON.stringify 无法满足的特定要求。
  • @RobG:我已经更新了我的问题。对我来说最好的是低磁盘使用率和弹性(我的树中可以有多种类型的对象)

标签: javascript serialization


【解决方案1】:

JSON 没有作为数据类型的功能。您只能序列化字符串、数字、对象、数组和布尔值(以及null

您可以创建自己的toJson 方法,只传递真正需要序列化的数据:

Person.prototype.toJson = function() {
    return JSON.stringify({age: this.age});
};

反序列化类似:

Person.fromJson = function(json) {
    var data = JSON.parse(json); // Parsing the json string.
    return new Person(data.age);
};

用法是:

var serialize = p1.toJson();
var _p1 = Person.fromJson(serialize);
alert("Is old: " + _p1.isOld());

为了减少工作量,您可以考虑将所有需要序列化的数据存储在每个Person 实例的特殊“数据”属性中。例如:

function Person(age) {
    this.data = {
        age: age
    };
    this.isOld = function (){
        return this.data.age > 60 ? true : false;
    }
}

那么序列化和反序列化只是调用JSON.stringify(this.data),设置实例的数据为instance.data = JSON.parse(json)

这将使toJsonfromJson 方法保持简单,但您必须调整其他功能。


旁注:

您应该将isOld 方法添加到函数的原型中:

Person.prototype.isOld = function() {}

否则,每个实例都有它自己的函数实例,这也会增加内存。

【讨论】:

  • 但是您可以将 JavaScript 函数转换为字符串,然后将该字符串存储在 JSON 中。然后,当您解析 JSON 时,您可以将字符串转换回函数并执行它。
  • @aroth:但是你为什么要这么做呢?它 (a) 增加了所需的存储量 (b) 是多余的。如果存储 100 个人,为什么要存储函数的相同字符串表示 100 次?只有数据应该被序列化。在这种情况下,函数不是数据。
  • @aroth:我的想法和菲利克斯一样。我不想存储函数。我有一个很大的对象树,我需要优化磁盘/处理器的使用。顺便说一句,谢谢!
  • @Topera:不客气。我不能说这是否是“最好”的方式,但我希望它能给你一些想法。
  • @Felix - 我不知道你为什么要这样做,我的意思不是应该这样做,只是技术上可以在 JSON 中序列化和反序列化函数。这是一个工作演示,如果有人好奇的话:jsfiddle.net/8FQzm/4
【解决方案2】:

我写 serialijse 是因为我遇到了和你一样的问题。

你可以在https://github.com/erossignon/serialijse找到它

它可以在 nodejs 或浏览器中使用,并且可以用于将一组复杂的对象从一个上下文 (nodejs) 序列化和反序列化到另一个上下文 (浏览器),反之亦然。

var s = require("serialijse");


var assert = require("assert");


// testing serialization of a simple javascript object with date
function testing_javascript_serialization_object_with_date() {

    var o = {
        date: new Date(),
        name: "foo"
    };
    console.log(o.name, o.date.toISOString());

    // JSON will fail as JSON doesn't preserve dates
    try {
        var jstr = JSON.stringify(o);
        var jo = JSON.parse(jstr);
        console.log(jo.name, jo.date.toISOString());
    } catch (err) {
        console.log(" JSON has failed to preserve Date during stringify/parse ");
        console.log("  and has generated the following error message", err.message);
    }
    console.log("");



    var str = s.serialize(o);
    var so = s.deserialize(str);
    console.log(" However Serialijse knows how to preserve date during serialization/deserialization :");
    console.log(so.name, so.date.toISOString());
    console.log("");
}
testing_javascript_serialization_object_with_date();


// serializing a instance of a class
function testing_javascript_serialization_instance_of_a_class() {

    function Person() {
        this.firstName = "Joe";
        this.lastName = "Doe";
        this.age = 42;
    }

    Person.prototype.fullName = function () {
        return this.firstName + " " + this.lastName;
    };


    // testing serialization using  JSON.stringify/JSON.parse
    var o = new Person();
    console.log(o.fullName(), " age=", o.age);

    try {
        var jstr = JSON.stringify(o);
        var jo = JSON.parse(jstr);
        console.log(jo.fullName(), " age=", jo.age);

    } catch (err) {
        console.log(" JSON has failed to preserve the object class ");
        console.log("  and has generated the following error message", err.message);
    }
    console.log("");

    // now testing serialization using serialijse  serialize/deserialize
    s.declarePersistable(Person);
    var str = s.serialize(o);
    var so = s.deserialize(str);

    console.log(" However Serialijse knows how to preserve object classes serialization/deserialization :");
    console.log(so.fullName(), " age=", so.age);
}
testing_javascript_serialization_instance_of_a_class();


// serializing an object with cyclic dependencies
function testing_javascript_serialization_objects_with_cyclic_dependencies() {

    var Mary = { name: "Mary", friends: [] };
    var Bob = { name: "Bob", friends: [] };

    Mary.friends.push(Bob);
    Bob.friends.push(Mary);

    var group = [ Mary, Bob];
    console.log(group);

    // testing serialization using  JSON.stringify/JSON.parse
    try {
        var jstr = JSON.stringify(group);
        var jo = JSON.parse(jstr);
        console.log(jo);

    } catch (err) {
        console.log(" JSON has failed to manage object with cyclic deps");
        console.log("  and has generated the following error message", err.message);
    }

    // now testing serialization using serialijse  serialize/deserialize
    var str = s.serialize(group);
    var so = s.deserialize(str);
    console.log(" However Serialijse knows to manage object with cyclic deps !");
    console.log(so);
    assert(so[0].friends[0] == so[1]); // Mary's friend is Bob
}
testing_javascript_serialization_objects_with_cyclic_dependencies();

【讨论】:

  • 感谢库,在没有require的情况下如何在浏览器上使用它?
  • 您现在可以使用 bower 安装 serialijsebower install serialijse
【解决方案3】:

我是https://github.com/joonhocho/seri的作者。

Seri 是 JSON + 自定义(嵌套)类支持。

您只需提供toJSONfromJSON 即可序列化和反序列化任何类实例。

这是一个嵌套类对象的示例:

import seri from 'seri';

class Item {
  static fromJSON = (name) => new Item(name)

  constructor(name) {
    this.name = name;
  }

  toJSON() {
    return this.name;
  }
}

class Bag {
  static fromJSON = (itemsJson) => new Bag(seri.parse(itemsJson))

  constructor(items) {
    this.items = items;
  }

  toJSON() {
    return seri.stringify(this.items);
  }
}

// register classes
seri.addClass(Item);
seri.addClass(Bag);


const bag = new Bag([
  new Item('apple'),
  new Item('orange'),
]);


const bagClone = seri.parse(seri.stringify(bag));


// validate
bagClone instanceof Bag;

bagClone.items[0] instanceof Item;
bagClone.items[0].name === 'apple';

bagClone.items[1] instanceof Item;
bagClone.items[1].name === 'orange';

希望它有助于解决您的问题。

【讨论】:

    【解决方案4】:

    在您调用 JSON.stringify 后,浏览器的原生 JSON API 可能不会返回您的 idOld 函数,然而,如果可以自己对 JSON 进行字符串化(可能使用 Crockford's json2.js 而不是浏览器的 API),那么如果你有一个 JSON 字符串,例如

    var person_json = "{ \"age:\" : 20, \"isOld:\": false, isOld: function() { return this.age > 60; } }";
    

    那你就可以打电话了

    eval("(" + person + ")") 
    

    ,你将在 json 对象中取回你的函数。

    【讨论】:

      【解决方案5】:

      我遇到了完全相同的问题,并编写了一个小工具来混合数据和模型。见https://github.com/khayll/jsmix

      你会这样做:

      //model object (or whatever you'd like the implementation to be)
      var Person = function() {}
      Person.prototype.isOld = function() {
          return this.age > RETIREMENT_AGE;
      }
      
      //then you could say:
      var result = JSMix(jsonData).withObject(Person.prototype, "persons").build();
      
      //and use
      console.log(result.persons[3].isOld());
      

      它可以处理复杂的对象,比如递归的嵌套集合。

      至于序列化JS函数,出于安全考虑,我不会这样做。

      【讨论】:

        【解决方案6】:

        我已经向 GitHub 添加了另一个 JavaScript 序列化程序存储库。

        这里的方法是将 JavaScript 对象序列化为原生 JavaScript,而不是采用将 JavaScript 对象序列化和反序列化为内部格式的方法。这样做的好处是格式与序列化程序完全无关,并且可以简单地通过调用 eval() 重新创建对象。

        https://github.com/iconico/JavaScript-Serializer

        【讨论】:

          【解决方案7】:

          我遇到了类似的问题,由于找不到足够的解决方案,我还为 javascript 创建了一个序列化库:https://github.com/wavesoft/jbb(事实上它有点多,因为它主要用于捆绑资源)

          它与 Binary-JSON 很接近,但它增加了一些额外的功能,例如被编码对象的元数据和一些额外的优化,如重复数据删除、对其他包的交叉引用和结构级压缩。

          但是有一个问题:为了使包大小保持较小,包中没有类型信息。此类信息在单独的“配置文件”中提供,描述您的编码和解码对象。出于优化原因,此信息以脚本的形式提供。

          但您可以使用 gulp-jbb-profile (https://github.com/wavesoft/gulp-jbb-profile) 实用程序从简单的 YAML 对象规范生成编码/解码脚本,让您的生活更轻松,如下所示:

          # The 'Person' object has the 'age' and 'isOld'
          # properties
          Person:
            properties:
              - age
              - isOld
          

          例如,您可以查看jbb-profile-three 个人资料。 准备好个人资料后,您可以像这样使用 JBB:

          var JBBEncoder = require('jbb/encode');
          var MyEncodeProfile = require('profile/profile-encode');
          
          // Create a new bundle
          var bundle = new JBBEncoder( 'path/to/bundle.jbb' );
          
          // Add one or more profile(s) in order for JBB
          // to understand your custom objects
          bundle.addProfile(MyEncodeProfile);
          
          // Encode your object(s) - They can be any valid
          // javascript object, or objects described in
          // the profiles you added previously.
          
          var p1 = new Person(77);
          bundle.encode( p1, 'person' );
          
          var people = [
                  new Person(45),
                  new Person(77),
                  ...
              ];
          bundle.encode( people, 'people' );
          
          // Close the bundle when you are done
          bundle.close();
          

          你可以这样读:

          var JBBDecoder = require('jbb/decode');
          var MyDecodeProfile = require('profile/profile-decode');
          
          // Instantiate a new binary decoder
          var binaryLoader = new JBBDecoder( 'path/to/bundle' );
          
          // Add your decoding profile
          binaryLoader.addProfile( MyDecodeProfile );
          
          // Add one or more bundles to load
          binaryLoader.add( 'bundle.jbb' );
          
          // Load and callback when ready
          binaryLoader.load(function( error, database ) {
          
              // Your objects are in the database
              // and ready to use!
              var people = database['people'];
          
          });
          

          【讨论】:

            【解决方案8】:

            我尝试使用 Date 和原生 JSON 来做到这一点...

            function stringify (obj: any) {
              return JSON.stringify(
                obj,
                function (k, v) {
                  if (this[k] instanceof Date) {
                    return ['$date', +this[k]]
                  }
                  return v
                }
              )
            }
            
            function clone<T> (obj: T): T {
              return JSON.parse(
                stringify(obj),
                (_, v) => (Array.isArray(v) && v[0] === '$date') ? new Date(v[1]) : v
              )
            }
            

            这说明了什么?它说

            • 如果您希望它更安全,则需要一个唯一标识符,比 $date 更好。
            class Klass {
              static fromRepr (repr: string): Klass {
                return new Klass(...)
              }
            
              static guid = '__Klass__'
            
              __repr__ (): string {
                return '...'
              }
            }
            

            这是一个可序列化的Klass,带有

            function serialize (obj: any) {
              return JSON.stringify(
                obj,
                function (k, v) { return this[k] instanceof Klass ? [Klass.guid, this[k].__repr__()] : v }
              )
            }
            
            function deserialize (repr: string) {
              return JSON.parse(
                repr,
                (_, v) => (Array.isArray(v) && v[0] === Klass.guid) ? Klass.fromRepr(v[1]) : v
              )
            }
            

            我也尝试过使用 Mongo 样式的对象 ({ $date }),但在 JSON.parse 中失败了。提供k 不再重要...

            顺便说一句,如果您不关心库,可以使用 js-yaml 中的 yaml.dump / yaml.load。只要确保您以危险的方式进行操作即可。

            【讨论】:

              【解决方案9】:

              我制作了一个名为 esserializer 的 npm 模块来解决这个问题:在序列化期间以纯 JSON 格式保存 JavaScript 类对象值,而不存储任何函数。在序列化期间,唯一产生的开销是保存类名信息。因此,磁盘使用得到优化。

              在反序列化阶段后期,esserializer 可以递归反序列化对象实例,并保留所有类型/函数信息。它适用于浏览器和 Node.js 环境。

              在 OP 的情况下,代码会很简单:

              var ESSerializer = require('esserializer');
              
              function Person(age) {
                  this.age = age;
                  this.isOld = function (){
                      return this.age > 60;
                  }
              }
              // before serialize, ok
              var p1 = new Person(77);
              alert("Is old: " + p1.isOld());
              
              // serialize
              var serializedText = ESSerializer.serialize(p1);
              //...do something, or send the above serializedText to another JavaScript environment.
              // deserialize
              var deserializedObj = ESSerializer.deserialize(serializedText, [Person]);
              alert("Is old: " + deserializedObj.isOld());
              

              deserializedObj 是一个Person 实例,其中包含所有值/函数/超类信息。

              希望它能有所帮助。

              【讨论】:

                【解决方案10】:

                您可以创建类的空实例并使用Object.assign 为其赋值。

                let p1 = new Person(77);
                let serialized = JSON.stringify(p1);
                let deserialized = Object.assign(new Person(), JSON.parse(serialized))
                

                【讨论】:

                  猜你喜欢
                  • 2021-10-24
                  • 2014-03-02
                  • 2012-03-02
                  • 2016-12-30
                  • 2013-04-24
                  • 2011-07-09
                  • 2014-05-14
                  • 2010-12-30
                  • 1970-01-01
                  相关资源
                  最近更新 更多