序列化与反序列化

在ASP.NET AJAX中使用了JSON作为客户端与服务器端传递对象信息的方式。因此,在ASP.NET AJAX的客户端与服务器端均提供了序列化与反序列化的能力。了解这些内容的使用方法,可以说是使用与扩展ASP.NET AJAX所必须的能力。在这两篇文章里,我们就来看一下ASP.NET AJAX中的序列化与反序列化的能力。


一、客户端的序列化与反序列化能力:

  在ASP.NET AJAX中,为客户端提供序列化能力的是Sys.Serialization.JavaScriptSerializer类的serialize静态方法。这个方法能够将一个客户端对象序列化成为一个JSON字符串,它的使用方法非常简单。如下:
var jsonStr = Sys.Serialization.JavaScriptSerializer.serialize(obj);
  没有过多可说的内容,可能比较“有特点”的地方就是它对于客户端Date对象的序列化操作。如果我们调用下面的代码,会出现什么结果呢?
var jsonStr = Sys.Serialization.JavaScriptSerializer.serialize(new Date());
  得到的结果类似于是“"@1162814090119@"”,请注意两边还有双引号。这个是一个ASP.NET AJAX对于Date对象比较特殊的表示方法,如果在某些时候开发人员需要自己来“拼接”字符串时,就需要注意这一点。

  给ASP.NET AJAX客户端带来反序列化能力的就是Sys.Serialization.JavaScriptSerializer类的deserialize静态方法。如下:
var obj = Sys.Serialization.JavaScriptSerializer.deserialize(jsonStr);
  它事实上只是简单地调用了JavaScript内置的eval方法。当然,既然序列化时对于Date对象有特殊的表示方法,在反序列化时,也会考虑到这一点:Sys.Serialization.JavaScriptSerializer类的deserialize静态方法在调用Evail之前,会把“"@...@"”变成“new Date(...)”的形式,这就是标准的JSON字符串了。


二、JavaScriptTypeResolver与JavaScriptConverter:

  客户端的序列化和反序列化非常简单,我把它放在这里一并说明更像是为了让内容更加完整。而服务器端的序列化与反序列化就不是那么轻易的了,它涉及到大量的字符串操作,也涉及到一定的自定义能力。这才是这片文章想要着重说明的。

  ASP.NET AJAX提供的序列化和反序列化能力都是由Microsoft.Web.Script.Serialization这个命名空间下的类完成的。不过幸运的是,他们大都是内部类,真正能够给开发人员使用的只有JavaScriptSerializer类的数个方法而已。ASP.NET AJAX已经带给我们比较充足的序列化与反序列化的能力,我们只需要掌握它,知道它们是如何工作的,那一般也就足够了。

  不过要进入对于这些序列化与反序列化能力的了解,首先需要了解其它的两个类:JavaScriptTypeResolver和JavaScriptConverter。

1、JavaScriptTypeResolver

  JavaScriptTypeResolver是一个抽象类,虽然是第一次在Atlas多个Release中出现,但是它并不是一个新鲜事物。它的作用就相当于Atlas CTP中的IJavaScriptSerializeContext接口,甚至可以说只是换了类名和方法名(事实上,从一个接口转变为一个抽象类,这个做法让人摸不着头脑,因为现在的抽象类也不存在任何的实现)。这个类的作用是“将一个字符串,与一个特定的类进行关联,使字符串成为那个特定类的一个标识”。这个抽象类存在着两个方法:

String ResolveTypeId(Type):得到Type对象的标识字符串。
Type ResolveType(String):从字符串标识获取一个Type对象。
  可以看出,这两个方法是一对相反的操作。他们会分别运用在序列化于反序列化操作之中。如果对于这个类的作用还不是非常了解的话,那么可以看一下ASP.NET AJAX中这个抽象类的一个简单实现。那就是Microsoft.Web.Script.Serialization.SimpleTypeResolver类。它的代码如下:
public sealed class SimpleTypeResolver : JavaScriptTypeResolver
{
    public override Type ResolveType(string id)
    {
        return Type.GetType(id);
    }

    public override string ResolveTypeId(Type type)
    {
        if (type == null)
        {
            throw new ArgumentNullException("type");
        }

        return type.AssemblyQualifiedName;
    }
}
  SimpleTypeResolver的作用是将一个类的Assembly Qualified Name与一个类型关联了起来。但是个人认为千万不要使用这个类,如果用了这个类的话,Strong Named Assembly的信息不是都暴露出去了吗?Version,Culture,PublicKeyToken,“一个都不能少”。

2、JavaScriptConverter

  JavaScriptConverter类的作用是提供了开发人员自定义序列化与反序列化的能力,这一点对于操作含有循环引用的复杂对象尤其重要。在之前的文章中我分析过这个类,也有过这个类的使用示例。不过这个类在RTM Release中的功能被精简了。它的方法和属性被缩减成了三个:

IEnumerable<Type> SupportedTypes:只读属性,返回这个Converter所有能够支持的类。
object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer):
这个方法的第一个参数是一个字典,有朋友可能会认为这个字典和JSON字符串的表示非常的接近:由Dictionary和List嵌套而成,最底端的元素为一些基本类型对象。不过事实上不是如此。ASP.NET AJAX在反序列化一个JSON字符串时,如果出现了“{ "__type" : "...", ...}”这样的片断时,在将其转换为真正的JSON表示的Dictionary(只存在基本类型对象的Dictionary)之后,如果发现该Dictionary存在“__type”这个Key,那么就会设法在这个时候就将它转换为__type值表示的那个类型了。也就是说,JavaScriptConverter的Deserialize方法接受到的第一个参数字典中,也有可能已经是一个特殊的类型了。
第二个参数为转换的目标类型。而第三个参数,则是调用当前Deserialize方法的JavaScriptSerializer了,我们的一些反序列化操作可以委托给它执行,它已经关联好了web.config中配置的JavaScriptConverter。不过需要注意的就是,千万要避免下一步操作又没有改变地回到了当前的Deserialize方法,显然这样会出现死循环。
IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer):这个方法的作用相对纯粹一些,将obj对象转换为一个IDictionary<string, object>对象,在这个方法将结果返回后,ASP.NET AJAX会在这个Dictionary中添加“__type”的值,这样的话,在反序列化时也能够使用当前的JavaScriptConverter来进行相反的操作。

3、使用JavaScriptTypeResolver与JavaScriptConveter

  当定义了JavaScriptTypeResolver与JavaScriptConverter后,还需要将其添加进某个JavaScriptSerializer后才能生效。代码大致如下:
// 定义一个JavaScriptTypeResolver实例
JavaScriptTypeResolver resolver = new MyTypeResolver();

// 创建一个使用上面Resolver的JavaScriptSerializer
JavaScriptSerializer serializer = new JavaScriptSerializer(resolver);

// 创建一个JavaScriptConverter数组
JavaScriptConverter[] converters = new JavaScriptConverter[] { new MyConverter() };

// 将Converter关联到Serializer中
serializer.RegisterConverters(converters);

// 使用JavaScriptSerializer进行序列化或反序列化操作
serializer.Serialize(...);
  关于JavaScriptConverter的使用,还需要提一点,就是在web.config文件中可以进行一些配置。如下:
<jsonSerialization>
    <converters>
        <add name="..." type="..." />
        ...
    </converters>
</jsonSerialization>
  需要注意的是,有些朋友认为在web.config里进行了JavaScriptConverter配置后,这些Converter就会默认被运用在JavaScriptSerializer的使用上。但是事实上这些配置的Converter只会被运用在Web Service的访问上,如果新创建了一个JavaScriptSerializer,则需要重新分配,才能使JavaScriptConverter生效。


深入Atlas系列:探究序列化与反序列化能力(下) - JavaScriptSerializer

  在ASP.NET AJAX中,客户端的序列化与反序列能力由Sys.Serialization.JavaScriptSerializer类的serialize和deserialize两个静态方法提供。在服务器端,所有的序列化与反序列化能力,包括类型之间的转换,对于开发人员来说都是由JavaScriptSerializer类的几个方法实现的。从前一片文章里我们已经知道了两个辅助的类:JavaScriptTypeResolver和JavaScriptConverter,他们的作用分别是“映射类与类标识”,以及“提供特定类的序列化与反序列化能力”。在以后的文章里,我将通过两个示例来演示这两个类在Web Services Access中直接或者间接的使用方式。

  在某些情况下,我们还是需要使用JavaScriptSerializer类的方法来操作一个类型,例如使用JavaScriptConverter来自定义特定类的序列化或者反序列化,就需要使用JavaScriptSerializer类的方法,因此我们这次就详细看一下这个类的能力。

  在JavaScriptSerializer中,我们可以看到下面可以使用的方法或者构造函数,它们都是实例方法:

Icon Member Description
 JavaScriptSerializer() 构造函数,用于创建一个新的JavaScriptSerializer对象,不指定JavaScriptTypeResolver
 JavaScriptSerializer(JavaScriptTypeResolver) 构造函数,用于创建一个新的JavaScriptSerializer对象,并使用指定的JavaScriptTypeResolver来映射特定类型与标识字符串。
 ConvertToType<T>(Object) 将给定对象转化成类型T。
 Deserialize<T>(String) 将JSON字符串转化为类型T。
 DeserializeObject(String) 将JSON字符串转化为一个对象。
 MaxJsonLength 获取或者设置序列化时能够接受的JSON字符串的最大长度。
 RecursionLimit 获取或者设置在反序列化JSON字符串时递归的最大深度。
 RegisterConverters(IEnumerable<JavaScriptConverter>) 注册序列化过程中使用的JavaScriptConveter对象。
 Serialize(Object) 将一个对象序列化成JSON字符串。
 Serialize(Object, StringBuilder) 将一个对象序列化到一个StringBuilder中。

  在这里我们主要来看一下ConvertToType,Deserialize,DeserializeObject和Serialize的两个重载方法。


1、ConvertToType<T>(Object)

  ConvertToType<T>(Object)方法的作用是将一个Object对象转换为指定的对象T。这个Object对象主要的转换,在其内部是直接调用了ObjectConverter.ConvertObjectToType(Object o, Type type, JavaScriptSerializer serializer)方法实现。ObjectConverter.ConvertObjectToType方法主要逻辑依次如下:

如果参数o为null,并且type为可以null的类型(例如引用类型,和.NET 2.0中的Nullable类型),则直接返回null。如果type不是可以为null的类型,但是type是char类型,那么返回'/0'。如果参数o不为null,则将继续下面的逻辑。
如果参数o是IDictionary<string, object>类型,则调用内部的ObjectConverter.ConvertDictionaryToObject方法将一个Dictionary<string, object>转换为type类型对象。
如果参数o是IList类型,则会调用内部的ObjectConverter.ConvertListToObject方法将IList类型转换为type类型对象。
如果type为null,或者参数o已经是type对象了,那么直接返回对象o。
使用TypeDescriptor.GetConverter方法获得type对应的TypeConverter,如果该TypeConverter能够转换o则转换并返回,否则会使用参数o的TypeConverter先将o转换为字符串(使用ConvertToInvariantString方法),再使用type对应的TypeConverter将该字符串转换为type类型(使用ConvertFromInvariantString方法)并返回。如果o的TypeConverter无法将o转换为字符串,或者type对应的TypeConverter无法将一个字符串转换为type类型,则只能检查o类型是否能够直接赋值给type类型。如果这也不行,那么只能抛出异常了。
  上面的逻辑其实不完整,在它的实现中事实上会在不少地方能够抛出异常,我则省略了这些逻辑的描述。

  在最后一步的“复杂”逻辑中,似乎能够使用提供TypeConverter来转换对象,以此自定义序列化与反序列化能力(事实上,如果单独使用这个方法时您的确可以这么做),但是事实上在实际使用中作用并不大。因为序列化与反序列化能力主要是应用在Web Service方法访问上的,而在这里的反序列化过程中很难使这段逻辑“遭遇”特殊的对象(虽然我们能够通过自定义JavaScriptConvetor来“遭遇”这种情况)。在这里,使用TypeConverter是为了转换一些“基础对象”,例如Int32,Double等。另外需要注意的是,我们不能使用ConvertToType方法直接转换客户端序列化的日期对象,因为日期在客户端会被序列化成“"@23552233@"”传递过来,在反序列化时需要做特殊处理。

  将IDictionary<string, object>和IList转化成特定对象的逻辑比较重要,尤其是前者。我们先来看一下它的主要逻辑吧:

如果在这个字典中存在“__type”对应的字符串(如果不是字符串,会将其转换为字符串,但是这种情况毫无意义),则会使用serializer中使用的JavaScriptTypeResolver,以“__type”的值作为类型的标示,以获得真正需要转化为的目标类型。这时,目标对象可能已经是新的类型了,我们称之为realType。
如果realType类型在serializer中存在对应的JavaScriptConverter,则使用特定的Converter反序列化对象,并返回。
最后,如果原始的type为字典或泛型字典,则会将这个原始IDictionary<string, object>转换为type对象。否则就会将构造一个realType类型的对象(需要注意的是这个类型必须有无参数的构造函数),然后会通过反射机制为public的属性或者变量(属性优先)。在这里,如果原始IDictionary<string, object>的key相对于目标类型的属性和变量相比有多余,也不会抛出异常。很自然,在将原始IDictionary<string, object>的value转换为目标字典中的value类型,或者目标类型的属性和变量的类型时,会递归调用ObjectConverter.ConvertObjectToType方法。
  事实上,这个IDictionary<string, object>对象代表了一个JSON字符串到特定类型的“中间状态”,上面这段逻辑可以说被大量运用在Web Services方法的访问中,这也就是为什么第一步会判断有没有“__type”的定义,因为这是在客户端指定服务器端特定类型的做法。这种IDictionary<string, object>到Object转换,是反序列化过程的一部分。

  相对来说,IList到Object的转换就比较简单了,它能够支持的对象类型有Array,ArrayList,List,List<T>和其余实现IList的类型。注意能够在服务器端反序列化的类型都必须有无参数的构造函数,在转换时依旧会递归调用ObjectConverter.ConvertObjectToType方法。


2、Deserialize<T>(String)

  该方法的作用是将一个JSON字符串转化为类型T。

  该方法的第一步,是首先将该字符串转化成为一个中间类型。这个类型可能是个基础类型(Int32,Double,DateTime等),或者IDictionary<string, object>与IList的互相嵌套(这就是JSON字符串的表示形式,一般来说,最终目标也是基础类型)。但是需要注意是,在将一个“{...}”形式的字符串片断转换为IDictionary<string, object>之后,如果发现该字典中有关于“__type”的定义,就会调用ObjectConverter.ConvertObjectToType方法立即将其转换为__type表示的类型,在这个过程中会将调用Deserialize<T>(String)方法的JavaScriptSerializer对象在各个操作中进行传递,因此起初在那个JavaScriptSerializer对象中定义的JavaScriptTypeResolver和JavaScriptConverter都会产生效果。

  该方法的第二步,就是将第一步所得到的结果,使用ObjectConverter.ConvertObjectToType方法将其转换为目标T了。可以发现,由于JSON字符串中“__type”的作用,还是能够在之前描述过的ObjectConverter.ConvertObjectToType逻辑的第5步中,使TypeConverter起到所需的效果的。如果合理使用,就能够很方便的进行开发。


3、DeserializeObject(String)

  该方法可以说是Deserialize<T>(String)方法的一小部分,它也分作两步进行。其中第一步和Deserialize<T>(String)方法第一步作用完全一样,而第二步也是使用了ObjectConverter.ConvertObjectToType方法进行转换。由于和那个方法相比没有指定目标对象T,因此传递给ObjectConverter.ConvertObjectToType方法的第二个参数则为null,也就是说,如果进行到IDictionary<string, object>到Object的转换,如果没有指定“__type”,它将保持不变。至于“[...]”类型的JSON字符串,如果没有指定type,则会默认转换为一个Object数组。


4、Serialize的两个重载方法

  这两个方法的作用是使用Object转换为JSON字符串。Serialize(Object)方法会构造一个StringBuilder,再调用Serialize(Object, StringBuilder)方法得到结果,因此我们将目光对准后者。

  其实Serialize方法的逻辑相对于Deserialize方法来说简单了不少,毕竟拼接字符串的工作一般总是比解析字符串的工作要容易。Serialize方法本身也是个递归方法,会递归地序列化一个对象的public属性和变量,因此在序列化一个复杂对象时往往会出现“循环引用”的状况,这时候就就会抛出异常。这时候JavaScriptConverter就起到其作用了,在序列化某个类型时,会查找serializer中是否有其对应的JavaScriptConveter,如果有的话,则会通过这个JavaScriptConverter得到一个IDictionary<string, object>,然后再为这个字典添加“__type”的值,最后再将这个字典对象序列化输出。需要注意的是,得到“__type”值的方式是通过JavaScriptConverter中的JavaScriptTypeResolver来得到类型的标识字符串。这个逻辑和反序列化操作正好相反。

 

  

   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值