JavaScript类型化对象的序列化

8

我不太清楚在JavaScript中如何对类型化对象进行序列化/反序列化。例如,我有一个包含各种成员和数组的“MapLayer”对象。我已经编写了以下代码(但尚未测试)来尝试将其序列化:

MapLayer.prototype.serialize = function() {
   var result = "{tileset:tilesets." + tilesets.getTilesetName(this.tileset) + ",columns:" + this.columns + ",rows:" + this.rows + 
   ",offsetX:" + this.offsetX + ",offsetY:" + this.offsetY + ",currentX:" + this.currentX + ",currentY:" + this.currentY + 
   ",scrollRateX:" + this.scrollRateX + ",scrollRateY:" + this.scrollRateY + ",virtualColumns:" + this.virtualColumns + ",virtualRows:" + this.virtualRows +
   ",tiles:\"" + this.encodeTileData2() + "\"";
   for(key in this)
   {
      if(this[key] instanceof Sprite)
         result += "," + key + ":" + this[key].serialize();
   }
   return result;
}

我的问题是,生成的对象应该如何作为MapLayer对象进行反序列化,而不是作为通用对象进行反序列化。所有Sprite实例应该如何被反序列化为sprites。我应该使用“new MapLayer()”而不是“{}”吗?还是我只需要在序列化中包含对象的原型和构造函数属性?还有其他我遗漏的内容吗?我这样做是愚蠢的吗?我不使用通用序列化/反序列化代码有两个原因:
  1. 我想以优化的格式序列化瓷砖数据,而不是将每个瓷砖存储为一个基于十进制的字符串表示,并用逗号分隔它们。
  2. 我不想将瓷砖集序列化为在反序列化期间构建为新对象的对象,而是作为对现有对象的引用。使用我提出的代码是否可能实现这一点?
编辑:请原谅我缺乏正确的术语;JavaScript是我较少专业的语言之一。当我说“Typed Object”时,我的意思是具有构造函数的对象。在这个示例中,我的构造函数是:
function MapLayer(map, tileset, columns, rows, virtualColumns, virtualRows, offsetX, offsetY, scrollRateX, scrollRateY, priority, tileData) {
   this.map = map;
   this.tileset = tileset;
   this.columns = columns;
   this.rows = rows;
   this.offsetX = offsetX;
   this.offsetY = offsetY;
   this.currentX = offsetX;
   this.currentY = offsetY;
   this.scrollRateX = scrollRateX;
   this.scrollRateY = scrollRateY;
   if(tileData.length < columns * rows * 2)
      this.tiles = DecodeData1(tileData);
   else
      this.tiles = DecodeData2(tileData);
   this.virtualColumns = virtualColumns ? virtualColumns : columns;
   this.virtualRows = virtualRows ? virtualRows : rows;
}

编辑2:从Šime Vidas的回答中获取代码后,我添加了一个名为“Device”的相关对象:

function Device( description, memory ) {
    this.description = description;
    this.memory = memory;
}

function Person( name, sex, age, devices ) {
    this.name = name;
    this.sex = sex; 
    this.age = age;
    this.devices = devices;
}

Person.deserialize = function ( input ) {
    var obj = JSON.parse( input );
    return new Person( obj.name, obj.sex, obj.age, obj.devices );
};

var device = new Device( 'Blackberry', 64);
var device2 = new Device( 'Playstation 3', 600000);
var person = new Person( 'John', 'male', 25, [device, device2] );

var string = JSON.stringify(person);
console.log( string );

var person2 = Person.deserialize( string );
console.log( person2 );
console.log( person2 instanceof Person );

现在的问题是如何最好地整合这些依赖对象,因为再次提醒,JSON会丢失对象的“类型”(原型?)。 与其运行构造函数,为什么不改变序列化和反序列化函数以确保对象只需要像这样构建一次,而不是创建并复制?
Person.prototype.serialize = function () {
    var obj = this; 
    return '({ ' + Object.getOwnPropertyNames( this ).map( function ( key ) {
        var value = obj[key];
        if ( typeof value === 'string' ) { value = '"' + value + '"'; }
        return key + ': ' + value;
    }).join( ', ' ) + ',"__proto__":Person.prototype})'; 
};

Person.deserialize = function ( input ) {
    return eval( input );
};

编辑3: 我遇到的另一个问题是JSON在IE9中似乎无法工作。我正在使用此测试文件:

<html>
<head>
<title>Script test</title>
<script language="javascript">
console.log(JSON);
</script>
</head>
</html>

控制台输出如下:

SCRIPT5009: 'JSON' is undefined 
test.html, line 5 character 1

编辑4: 为了纠正JSON问题,我必须在开头包含正确的DOCTYPE标签。


一个具有除“Object”之外的构造函数的对象。我不知道该如何称呼它。 - BlueMonkMN
啊,你的意思是指那些作为自己构造函数实例的对象(MapLayerSprite等)…… - Šime Vidas
我的建议是添加一个 MapLayer.prototype.deserialize 方法,该方法将接受一个字符串并返回一个 MapLayer 对象。我正在编写一个示例... - Šime Vidas
那么在这种情况下应该避免使用JSON吗? - BlueMonkMN
JSON.stringify(yourMapLayerObj) 返回的字符串是否符合您的要求?如果不符合,那么显然您需要一个自定义的 stringify/parse 解决方案... - Šime Vidas
或者在你的构造函数中添加一个toJSON方法。 - Zev Spitz
3个回答

8

首先,这里有一个简单的自定义序列化/反序列化示例:

function Person( name, sex, age ) {
    this.name = name;
    this.sex = sex;
    this.age = age;
}

Person.prototype.serialize = function () {
    var obj = this;
    return '{ ' + Object.getOwnPropertyNames( this ).map( function ( key ) {
        var value = obj[key];
        if ( typeof value === 'string' ) { value = '"' + value + '"'; }
        return '"' + key + '": ' + value;
    }).join( ', ' ) + ' }';
};

Person.deserialize = function ( input ) {
    var obj = JSON.parse( input );
    return new Person( obj.name, obj.sex, obj.age );
};

使用方法:

首先,我们需要创建一个新的实例对象:

var person = new Person( 'John', 'male', 25 );

现在,我们使用 Person.prototype.serialize 方法将该对象序列化为字符串:
var string = person.serialize();

这将会给我们返回这个字符串:
{ "name": "John", "sex": "male", "age": 25 }

最后,我们使用 Person.deserialize 静态方法对该字符串进行反序列化:
var person2 = Person.deserialize( string );

现在,person2是一个Person实例,并包含与原始的person实例相同的属性值。
实时演示: http://jsfiddle.net/VMqQN/
现在,虽然无论如何都需要Person.deserialize静态方法(它在内部使用JSON.parse,并调用Person构造函数来初始化一个新实例),但另一方面,只有在内置的JSON.stringify静态方法不够用时才需要Person.prototype.serialize方法。
在上面的示例中,var string = JSON.stringify( person )也可以完成任务,因此不需要自定义序列化机制。请参见这里:http://jsfiddle.net/VMqQN/1/ 但是,您的情况更加复杂,因此需要定义一个自定义序列化函数。

控制台报告了一个错误,使用我添加到问题中的测试HTML脚本。 - BlueMonkMN
1
@BlueMonkMN 这是因为你没有定义文档类型。在你的HTML文档的第一行添加 <!doctype html>。如果未定义有效的文档类型,IE将进入怪异模式... - Šime Vidas
好的,感谢你的回答和帮助。我只是想确认一下,JSON并不像在.NET中使用二进制序列化那样真正意义上的“免费序列化”。如果你想要进行任何复杂的操作,似乎你需要在其上添加一些其他的序列化代码。顺便问一下,垃圾回收是否会有效地清除在反序列化过程中构造的所有临时对象? - BlueMonkMN
@BlueMonkMN 在JavaScript中,规则是只要存在至少一个引用,对象就会在内存中存在。例如,在上面我的代码中的Person.deserialize函数内部,声明了一个本地变量obj,并将由JSON.parse返回的对象分配给它。现在,由于本地变量是该对象的唯一引用,因此一旦销毁本地变量(即函数返回时),对象将被垃圾回收。 - Šime Vidas
@ŠimeVidas 我再也无法让它工作了,而且我已经丢失了曾经表明它可以工作的代码,所以我一定犯了一些错误或者遇到了一些我无法重现的晦涩情况。抱歉。 - BlueMonkMN
显示剩余7条评论

1

如果您查看ST-JS(http://st-js.org)项目,它允许在服务器端使用Java创建对象图形,将其序列化为JSON,并在客户端(Javascript)中以类型化的方式反序列化它,即对象将使用它们的构造函数实例化,您可以在创建的对象上调用方法。

要使用ST-JS,您应该编写您的客户端代码是Java,几乎一对一地转换为Javascript。

网站主页上的AJAX / JSON章节解释了如何解析JSON字符串同时保留类型信息。


我认为 OP 是在询问如何利用他们现有的代码库,而不是迁移到不同的框架。 - Richard Marr
是的,你有点说对了。我指出的问题是,为了以通用的方式反序列化“类型化”对象的图形(如在Edit 2中),您需要以某种方式存储有关您的“类型化对象”的一些元数据,即告诉您的反序列化器例如,“Person”的“devices”属性是一个“Device”数组。 - alex.c
如果您查看我们的源代码,可以轻松提取的parseJSON函数使用了一个名为"typeDescription"的静态字段,它是一个简单的映射 - 我们的框架生成了这个映射,但也可以手动创建。 - alex.c

0
我的问题是,生成的对象应该如何反序列化为MapLayer对象而不是通用对象。所有Sprite实例应该如何反序列化为sprites。
我已经创建了一个名为esserializer的npm模块来解决这个问题:在普通JSON格式中保存JavaScript类实例值以及其类名信息,以便在序列化期间进行反序列化。
const ESSerializer = require('esserializer');
const serializedText = ESSerializer.serialize(anInstanceOfClassMapLayer);

后来,在反序列化阶段(可能在另一台机器上),esserializer可以递归地反序列化对象实例,并保留所有类/属性/方法信息,使用相同的类定义:

const deserializedObj = ESSerializer.deserialize(serializedText, [MapLayer, Sprite]);
// deserializedObj is a perfect copy of anInstanceOfClassMapLayer

我应该使用"new MapLayer()"而不是"{}"吗?还是说我只需要在序列化中包含对象的原型和构造函数属性即可?
在ESSerializer内部,类名信息在序列化期间被包含,并且所有原型属性在反序列化期间被重新构建。
我不想将tileset序列化为在反序列化期间作为新对象构建的对象,而是作为对现有对象的引用。
不幸的是,这是不可能的。序列化发生在一台计算机上,而反序列化可能发生在另一台计算机上——无论如何,这就是序列化/反序列化所要做的事情。计算机永远不会知道另一台计算机上的“现有对象”。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接