探究序列化與反序列化能力(下) - JavaScriptSerializer

  在ASP.NET AJAX中,客戶端的序列化與反序列能力由Sys.Serialization.JavaScriptSerializer類的serialize和deserialize兩個靜態方法提供。在服務器端,所有的序列化與反序列化能力,包括類型之間的轉換,對於開發人員來說都是由JavaScriptSerializer類的幾個方法實現的。從前一片文章裏我們已經知道了兩個輔助的類:JavaScriptTypeResolver和JavaScriptConverter,他們的作用分別是“映射類與類標識”,以及“提供特定類的序列化與反序列化能力”。在以後的文章裏,我將通過兩個示例來演示這兩個類在Web Services Access中直接或者間接的使用方式。

  在某些情況下,我們還是需要使用JavaScriptSerializer類的方法來操作一個類型,例如使用JavaScriptConverter來自定義特定類的序列化或者反序列化,就需要使用JavaScriptSerializer類的方法,因此我們這次就詳細看一下這個類的能力。

  在JavaScriptSerializer中,我們可以看到下面可以使用的方法或者構造函數,它們都是實例方法:

Icon Member Description
pubmethod.gif JavaScriptSerializer() 構造函數,用於創建一個新的JavaScriptSerializer對象,不指定JavaScriptTypeResolver
pubmethod.gif JavaScriptSerializer(JavaScriptTypeResolver) 構造函數,用於創建一個新的JavaScriptSerializer對象,並使用指定的JavaScriptTypeResolver來映射特定類型與標識字符串。
pubmethod.gif ConvertToType<T>(Object) 將給定對象轉化成類型T。
pubmethod.gif Deserialize<T>(String) 將JSON字符串轉化爲類型T。
pubmethod.gif DeserializeObject(String) 將JSON字符串轉化爲一個對象。
pubproperty.gif MaxJsonLength 獲取或者設置序列化時能夠接受的JSON字符串的最大長度。
pubproperty.gif RecursionLimit 獲取或者設置在反序列化JSON字符串時遞歸的最大深度。
pubmethod.gif RegisterConverters(IEnumerable<JavaScriptConverter>) 註冊序列化過程中使用的JavaScriptConveter對象。
pubmethod.gif Serialize(Object) 將一個對象序列化成JSON字符串。
pubmethod.gif 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方法主要邏輯依次如下:
  1. 如果參數o爲null,並且type爲可以null的類型(例如引用類型,和.NET 2.0中的Nullable類型),則直接返回null。如果type不是可以爲null的類型,但是type是char類型,那麼返回'/0'。如果參數o不爲null,則將繼續下面的邏輯。
  2. 如果參數o是IDictionary<string, object>類型,則調用內部的ObjectConverter.ConvertDictionaryToObject方法將一個Dictionary<string, object>轉換爲type類型對象。
  3. 如果參數o是IList類型,則會調用內部的ObjectConverter.ConvertListToObject方法將IList類型轉換爲type類型對象。
  4. 如果type爲null,或者參數o已經是type對象了,那麼直接返回對象o。
  5. 使用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轉化成特定對象的邏輯比較重要,尤其是前者。我們先來看一下它的主要邏輯吧:
  1. 如果在這個字典中存在“__type”對應的字符串(如果不是字符串,會將其轉換爲字符串,但是這種情況毫無意義),則會使用serializer中使用的JavaScriptTypeResolver,以“__type”的值作爲類型的標示,以獲得真正需要轉化爲的目標類型。這時,目標對象可能已經是新的類型了,我們稱之爲realType
  2. 如果realType類型在serializer中存在對應的JavaScriptConverter,則使用特定的Converter反序列化對象,並返回。
  3. 最後,如果原始的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來得到類型的標識字符串。這個邏輯和反序列化操作正好相反。

  這些就是ASP.NET AJAX服務器端提供的序列化與反序列化的能力。而且事實上它提供的擴展能力往往也已經足夠了,這點着實爲我們省去了許許多多的麻煩。

原文地址:http://www.cnblogs.com/JeffreyZhao/archive/2006/11/10/Inside_Atlas_Series__Investigate_the_Serialization_and_Deserialization_Ability_2.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章