stringify javascript函数

时间:2013-12-12 02:17:21

标签: javascript json

我正处于游戏开发的最后阶段,我有很多像这样的对象;

roomBedroom = function () {
    this.title = "Bedroom";
    this.description = "I'm in a bedroom";
    this.noun = "bed";
    this.entities = new Array();
}

var bedroom = new roomBedroom();

我现在要做的是将所有游戏对象放入数组中;

var savedGameObjects = {};
savedGameObjects['bedroom'] = bedroom;
var jsonGame = JSON.stringify(savedGameObjects);

计划是保存savedGameObjects数组,然后在用户再次加载游戏时调用它。

如果我将savedGameObjects['bedroom'] = bedroom;替换为savedGameObjects['bed'] = 'slappy';,它可以正常工作,但是当我有对象时却不行。

我真的需要将对象保存在当前状态。我宁愿不通过每个对象逐个保存关键信息。

5 个答案:

答案 0 :(得分:1)

这感觉有点像黑客,但它是我现在能想到的最好的

您的序列化/反序列化实用程序

这将在序列化之前将obj.constructor.name附加到obj.__prototype。反序列化后,原型将被放回原位。

(function(global) {

  function serialize(obj) {
    obj.__prototype = obj.constructor.name;
    return JSON.stringify(obj);
  };

  function deserialize(json) {
    var obj = JSON.parse(json);
    obj.__proto__ = global[obj.__prototype].prototype;
    return obj;
  }

  global.serialize = serialize;
  global.deserialize = deserialize;

})(window);

示例“类”

(function(global) {

  function Foo() {
    this.a = "a";
    this.b = "b";
  }

  Foo.prototype.hello = function() {
    console.log("hello");
  }

  global.Foo = Foo;

})(window);

让我们试一试

var foo = new Foo();

var json = serialize(foo);
console.log(json);

var newFoo = deserialize(json);
console.log('a', newFoo.a); // a
console.log('b', newFoo.b); // b

newFoo.hello(); // hello

留意一些问题

如果使用表达式来定义“类”,则将具有无名构造函数

var Foo = function() {};
var foo = new Foo();
foo.constructor.name; // ""

与命名函数

相对
function Foo() {}
var foo = new Foo();
foo.constructor.name; // Foo

为了使serializedeserialize有效,您需要使用命名函数


另一个问题

deserialize方法希望您的“类”存在于同一名称空间中(在本例中为window)。您可以用另一种方式封装游戏对象类,只需确保重新配置deserialize方法,以便它可以根据需要找到原型。


改善这种情况

不是将serialize附加到全局window,而是serialize可以使GameObject.prototype继续存在(例如)GameObject,然后您的各个类可以继承var json = foo.serialize(); // {"a":"a","b":"b","__prototype":"Foo"} }。然后序列化对象就像

一样简单
deserialize

然后,您可以将GameObject.deserialize定义为foo,然后恢复var foo = GameObject.deserialize(json);

var savedData = // your normal JSON here

var player = Player.create(savedData.player);

var items = [];
for (var i=0, i<savedData.items.length; i++) {
  items.push(Item.create(savedData.items[i]));
}

var map = Map.create(savedData.map);

另一种解决方案

您可以非常巧妙地使用Factory Method Pattern,而不是实现自定义序列化程序和反序列化程序。

这可能有点冗长,但是它确实让你可以控制游戏对象的反序列化/恢复方式。

{{1}}

这是一个非常有趣的问题,我相信你不是第一个遇到它的人。我真的很想知道其他人的想法。

答案 1 :(得分:0)

如果我在浏览器中运行以下代码,获取卧室对象的JSON字符串没有问题,不确定问题是什么。

请注意,JSON是数据,卧室是对象,卧室可能具有JSON没有的turnOffLight()行为。

roomBedroom = function () {
    this.title = "Bedroom";
    this.description = "I'm in a bedroom";
    this.noun = "bed";
    this.entities = new Array();
}

var bedroom = new roomBedroom();

var savedGameObjects = {};
savedGameObjects['bedroom'] = bedroom;
//logs {"bedroom":{"title":"Bedroom","description":
//  "I'm in abedroom","noun":"bed","entities":[]}}
console.log(JSON.stringify(savedGameObjects));

因此,如果您想从JSON 数据重新创建对象实例,那么您可以更改构造函数:

roomBedroom = function (args) {
    //following fails fast and loud, you could silently
    //fail by setting args to {}
    if(typeof args!=="object")
      throw new Error("Have to create roomBedroom by passing an object");
      //or do args={} to silently fail
    this.title = args.title||"Bedroom";
    this.description = args.description||"I'm in a bedroom";
    this.noun = args.noun||"bed";
    //if entities are objects with behavior
    //  you have to re create them here passing the JSON data
    //  as I've done with roomBedroom
    this.entities = args.entities||new Array();
}
var jsonString='{"bedroom":{"title":"Bedroom",'+
  '"description":"I\'m in a bedroom",'+
  '"noun":"bed","entities":[]}}';
var bedroom = new roomBedroom({});
bedroom.entities.push({hi:"there"});
bedroom.title="Master Bedroom";
//serialize bedroom to a json string
var jsonString = JSON.stringify(bedroom);
//create a roomBedroom instance named br2 using
// the serialized string
var br2=new roomBedroom(JSON.parse(jsonString));

//compare if they are the same
console.log(JSON.stringify(bedroom)===JSON.stringify(br2));//true

答案 2 :(得分:0)

我有一种可能适合你的方法。您可以 see it in action on JSFiddle

重点是在解析对象时使用 reviver parameter JSON.parse重建对象。

我使用可以为多种不同类型配置的通用reviver执行此操作,尽管此处使用的唯一一种是RoomBedroom构造函数。此实现假定您具有使用对现有对象的引用来创建新对象的简单复制构造函数。 (对于其他更复杂的可能性,请参阅我在2月份给出的 an answer to another question 。)为了让复制构造函数变得容易,我还有一个函数接受一个非常简单的构造函数,一组默认值,并为您构建一个复制构造函数。

var MultiReviver = function(types) {
    return function(key, value) {
        var type;
        for (var i = 0; i < types.length; i++) {
            type = types[i];
            if (type.test(value)) {
                return new type.constructor(value);
            }
        }
        return value;
    };
};


var makeCloningConstructor = (function() {
    var clone = function(obj) {return JSON.parse(JSON.stringify(obj));};
    var F = function() {};

    return function(Constructor, defaults) {
        var fn = function(obj) {
            Constructor.call(this);
            var self = this;
            var config = obj || {};
            Object.keys(defaults).forEach(function(key) {
                self[key] = clone(defaults[key]);
            });
            Object.keys(config).forEach(function(key) {
                self[key] = clone(config[key]);
            });
        };
        F.prototype = Constructor.prototype;
        fn.prototype = new F();
        fn.constructor = Constructor;

        return fn;
    };
})();

// Note: capitalize constructor functions
var RoomBedroom = makeCloningConstructor(function RoomBedroom() {}, {
    title: "Bedroom",
    description: "I'm in a bedroom",
    noun: "bed",
    entities: []  // Note: use `[]` instead of `new Array()`.
});

RoomBedroom.prototype.toggleLight = function() {
    this.lightOn = !this.lightOn;
};

RoomBedroom.prototype.checkLights = function() {
    return "light is " + (this.lightOn ? "on" : "off");
};

var bedroom = new RoomBedroom();
bedroom.windowCount = 3; // add new property
bedroom.noun = "king-sized bed"; // adjust property
bedroom.toggleLight(); // create new propery, use prototype function
console.log(bedroom.checkLights());

var savedGameObjects = {};
savedGameObjects['bedroom'] = bedroom;
var jsonGame = JSON.stringify(savedGameObjects);

var reviver = new MultiReviver([{
    constructor: RoomBedroom,
    test: function(obj) {
        var toString = Object.prototype.toString, str = "[object String]", 
            arr = "[object Array]";
        return toString.call(obj.title) == str && 
               toString.call(obj.description) == str && 
               toString.call(obj.noun) == str && 
               toString.call(obj.entities) == arr;
    } 
}]);

var retrievedGameObjects = JSON.parse(jsonGame, reviver);

// data comes back intact
console.log(JSON.stringify(retrievedGameObjects, null, 4));
// constructor is as expected
console.log("Constructor: " + retrievedGameObjects.bedroom.constructor.name);
// prototype functions work
console.log(retrievedGameObjects.bedroom.checkLights());

我不知道它是否正是您所寻找的,但我认为这至少是一种有趣的方法。

答案 3 :(得分:0)

更快的路线

从优化的角度来看,最好像Adeneo所说的那样,它可以通过一个可导出的简单对象为你的每个游戏对象提供动力,即:

roomBedroom = function(){
    this.data = {};
    this.data.title = 'Bedroom'
    /// and so on...
}

然后可以通过JSON轻松存储和重新导入它们。通过分析和覆盖数据属性。例如,您可以设置Maček提到的系统(+1),即为每个游戏对象提供序列化和反序列化功能:

roomBedroom.prototype.serialize = function(){
    return JSON.stringify( this.data );
};

roomBedroom.prototype.deserialize = function( jstr ){
    this.data = JSON.parse(jstr);
};


更快的方式

但是,您可以使用以下内容对您已有的内容进行简单的添加:

首先使用objectName属性增强游戏对象。这是因为constructor.namefunction.name是不可靠的,并且在你走的时候做得更奇怪,更好地使用你已经固定的字符串。

var roomBedroom = function ( title ) {
    this.objectName = "roomBedroom";
    this.title = title;
    this.description = "I'm in a bedroom";
    this.noun = "bed";
    this.entities = new Array();
};

然后是帮助存储的附加代码:

var storage = {};

/// add your supported constructors to this list, there are more programmatic
/// ways to get at the constructor but it's better to be explicit.
storage.constructors = {
    'roomBedroom' : roomBedroom
};

/// take an instance and convert to simple object
storage.to = function( obj ){
    if ( obj.toStorage ) {
        return obj.toStorage();
    }
    else {
        var keep = {};
        for ( var i in obj ) {
            if ( obj.hasOwnProperty(i) && !obj[i].call ) {
                keep[i] = obj[i];
            }
        }
        return keep;
    }
}

/// take simple object and convert to an instance of constructor
storage.from = function( obj ){
    var n = obj && obj.objectName, c = storage.constructors[n];
    if ( n && c ) {
        if ( c.fromStorage ) {
            return c.fromStorage( obj );
        }
        else {
            var inst = new c();
            for ( var i in obj ) {
                if ( obj.hasOwnProperty(i) ) {
                    inst[i] = obj[i];
                }
            }
            return inst;
        }
    }
    else {
        throw new Error('`' + n + '` undefined as storage constructor');
    }
}

一旦你拥有它,就可以这样使用它:

var savedGameObjects = {};
    savedGameObjects['bedroom'] = storage.to(new roomBedroom("bedroom"));
    savedGameObjects['bedroom2'] = storage.to(new roomBedroom("bedroom2"));

var jsonGame = JSON.stringify(savedGameObjects);
console.log(jsonGame);

savedGameObjects = JSON.parse(jsonGame);
for( var i in savedGameObjects ) {
    savedGameObjects[i] = storage.from(savedGameObjects[i]);
    console.log(savedGameObjects[i]);
}


额外

您还可以通过分别在构造的实例和构造函数上提供toStoragefromStorage方法来具体了解对象的存储/未存储方式。例如,如果您只想存储roomBedrooms的标题,则可以使用以下内容。显然这是一个不切实际的用例,你经常使用它来避免存储缓存或计算的子对象和属性。

roomBedroom.prototype.toStorage = function( obj ){
    var ret = {};
    ret.title = obj.title;
    return ret;
};

roomBedroom.fromStorage = function( obj ){
    var inst = new roomBedroom();
    inst.title = obj.title;
    return inst;
};

以上也意味着您可以通过提供参数来改进游戏对象构造,而不是迭代可能很慢且容易出错的属性。

roomBedroom.fromStorage = function( obj ){
    return new roomBedroom( obj.title );
};

甚至:

roomBedroom.fromStorage = function( obj ){
    return new roomBedroom( obj ); // <-- the constructor processes the import.
};


拨弄

http://jsfiddle.net/XTUdp/


声明

上面的代码依赖于hasOwnProperty的存在,但是它不存在跨浏览器,应该使用polyfill直到它......或者,如果你没有做任何复杂的原型继承你不用担心,可以从代码中删除它。

答案 4 :(得分:-2)

你可以声明一个像

这样的大变量
var world = {};

并且每个小变量都声明为

var bedroom = world.bed = (world.bed || new roomBedroom());

记住永远不要将卧室更换为另一个物体,我认为这样可以正常工作,但看起来太长了啰嗦