Hello World
Spiga

深入Atlas系列:探究序列化与反序列化能力(上) - 客户端支持,JavaScriptTypeResolver与JavaScriptConverter

2006-11-06 23:41 by 老赵, 4161 visits
  在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接口,甚至可以说只是换了类名和方法名(事实上,从一个接口转变为一个抽象类,这个做法让人摸不着头脑,因为现在的抽象类也不存在任何的实现)。这个类的作用是“将一个字符串,与一个特定的类进行关联,使字符串成为那个特定类的一个标识”。这个抽象类存在着两个方法:
  1. String ResolveTypeId(Type):得到Type对象的标识字符串。
  2. 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中的功能被精简了。它的方法和属性被缩减成了三个:
  1. IEnumerable<Type> SupportedTypes:只读属性,返回这个Converter所有能够支持的类。
  2. 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方法,显然这样会出现死循环。
  3. 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生效。


在下一篇文章中,我们将仔细分析JavaScriptSerializer中每一个可用的方法,以充分了解ASP.NET AJAX中提供的序列化于反序列化能力。
Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

18 条回复

  1. 老赵
    admin
    链接

    老赵 2006-11-06 23:48:00

    在Web Service中自定义序列化与反序列化操作的能力衰退得让人心寒。仔细察看代码后发现,已经完全无法在客户端序列化成一个带有循环引用的对象了……

  2. sunlife
    *.*.*.*
    链接

    sunlife 2006-11-06 23:51:00

    真是一个勤奋的人,向你学习

  3. TerryLee
    *.*.*.*
    链接

    TerryLee 2006-11-07 00:06:00

    支持一下:)

  4. 老赵
    admin
    链接

    老赵 2006-11-07 00:38:00

    @sunlife
    @TerryLee
    :)

  5. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2006-11-07 11:35:00

    我还是不太明白JavaScriptConverter与JavaScriptSerializer的关系,哪个时候该用哪个。

  6. 老赵
    admin
    链接

    老赵 2006-11-07 12:44:00

    @Cat Chen
    JavaScriptConverter不是用来直接使用的,它应该算是使用JavaScriptSerializer的一个辅助。当JavaScriptSerializer在某些情况下如果发现了正在操作的类型有对应的Converter,于是就会把部分序列化或者反序列化信息交给Converter完成。因此Converter一般用于处理带有循环引用的复杂对象,因为JavaScriptSerializer没有办法自己操作,会抛出InvalidOperationException.

  7. Hunts.C
    *.*.*.*
    链接

    Hunts.C 2006-11-07 13:25:00

    学习了 虽然还不大理解

  8. 老赵
    admin
    链接

    老赵 2006-11-07 17:51:00

    @Hunts.C
    哪里还不太清楚呢?我可以再说明一下。:)
    我最怕的就是写出来东西没有人理……

  9. 小蜗牛
    *.*.*.*
    链接

    小蜗牛 2006-11-07 22:10:00

    这种序列化可以解决什么问题呢?@@

  10. MK2
    *.*.*.*
    链接

    MK2 2006-11-09 01:08:00

    是否可以总结一下Asp.net Ajax自身能序列化那些.net基本类型呢?

  11. 老赵
    admin
    链接

    老赵 2006-11-09 01:15:00

    @MK2
    可以序列化任何没有循环引用的类型。:)

  12. Jawer[未注册用户]
    *.*.*.*
    链接

    Jawer[未注册用户] 2006-11-09 08:20:00

    感谢你精彩的文章~
    那么我想请问一下这两个类与ViewState的序列化有关系吗?
    ViewState应该如何操作呢?在Ajax中谢谢了.

  13. 老赵
    admin
    链接

    老赵 2006-11-09 10:06:00

    @Jawer
    我没有理解您的问题,您需要在ViewState中作哪些事情呢?ViewState的序列化使用的是自己的方法,与这两个类没有任何关系的,它们完全是ASP.NET AJAX的类,用于对象与JSON之间的转换。:)

  14. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2006-11-10 01:19:00

    @Jeffrey Zhao
    另外一些原子类型也要JavaScriptConverter转换吧,例如Guid。

  15. Cat Chen
    *.*.*.*
    链接

    Cat Chen 2006-11-10 01:22:00

    @Jawer
    这两个东西都和ViewState没关系吧,先不论它们在实现上的无关性,就算从使用角度来说也是无关的。

    JSON是用于客户端JavaScript调用服务器端WebService过程中的序列化,这时候客户端是主动方,以它为中心,View的概念也在客户端,那么ViewState就是没意义的。ViewState的存在意义是,服务器端需要有View的概念,但是跨页面生命周期时View的状态会丢失,所以我们要引入一个中间过程来保存这种状态,这就是ViewState。而当你在客户端时,服务器端的View就与你无关了。

  16. 老赵
    admin
    链接

    老赵 2006-11-10 01:28:00

    @Cat Chen
    其实应该是:没有办法使用ASP.NET AJAX反序列化的类都必须使用JavaScriptConverter,呵呵。比如没有无参数构造函数的类,比如无法通过反射来设置对象信息的类。

  17. xieex
    *.*.*.*
    链接

    xieex 2008-04-20 22:55:00

    赵老师,您好!
    我在用JavaScriptSerializer序列化某个对象为JSON字符串时,重新定义JavaScriptConveter了。
    但是有时程序会出现异常:
    异常详细信息: System.ArgumentException: RecursionLimit exceeded.
    我发现时JavaScriptSerializer对象的RecursionLimit 属性超出界限了,但是我找不出什么地方超出,我在序列化时进行了递归,但是不会进行100次递归(该属性默认为100次),你在文章中也讲到了该属性的意思,但是我还是不太明白该属性的意思,能否解释清楚一点,该属性应该根据什么来设置呢?

  18. asen[未注册用户]
    *.*.*.*
    链接

    asen[未注册用户] 2008-07-01 14:12:00

    不错,理解了。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我