在 JavaScript 中序列化/反序列化对象的最佳方法?

IT技术 javascript serialization
2021-03-06 05:42:41

我的应用程序中有许多 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 和另一个库来序列化/反序列化。
6个回答

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() {}

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

@Topera:不客气。我不能说这是否是“最好”的方式,但我希望它能给你一些想法。
2021-04-20 05:42:41
@aroth:我在想与 felix 相同的事情。我不想存储功能。我有一个非常大的对象树,我需要优化磁盘/处理器的使用。顺便说一下,谢谢!
2021-04-24 05:42:41
但是您可以将 JavaScript 函数转换为字符串,然后将该字符串存储在 JSON 中。然后当您解析 JSON 时,您可以将字符串转换回函数并执行它。
2021-05-01 05:42:41
@Felix - 我不知道你为什么想要这样做,我的观点不是应该完成,只是在技术上可以序列化和反序列化 JSON 中的函数。这是一个工作演示,如果有人好奇: jsfiddle.net/8FQzm/4
2021-05-09 05:42:41
@aroth:但是你为什么要这样做?它 (a) 增加了所需的存储量 (b) 是多余的。如果您存储 100 个人,为什么要存储 100 次函数的相同字符串表示?只有数据应该被序列化。在这种情况下,函数不是数据。
2021-05-11 05:42:41

我写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();
谢谢你的图书馆,你如何在没有要求的情况下在浏览器上使用它?
2021-04-24 05:42:41
您现在可以使用 bower 来安装serialijse.bower install serialijse
2021-04-26 05:42:41

我是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';

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

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

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

然后你可以打电话

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

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

我遇到了完全相同的问题,并编写了一个小工具来混合数据和模型。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函数,出于安全原因,我不会这样做。