Json的序列化與反序列化


  想想某一天,你在看LOL攻略的時候,系統突然崩潰了,接着瀏覽器出現了密密麻麻的LOL帳號和密碼,你一定在想:“天啊,這次要發財了,說不定里面有超凡號或者王者號,我得趕緊全部記下來。”然而說完你就驚呆了,那么多的帳號密碼,而且全部寫在了Json里面,一個一個復制粘貼要記到什么時候啊。。。如果這時候我在你身邊,我一定會幫助你的,前提是,要分幾個王者號給我噢。。。

  

 

   言歸正傳。

  上面舉的例子雖然有點不太現實,但其實是想和大家說明一個問題,如果要解析Json或XML,請不要使用檢索字符串的方式!!!

       那么在說明如何解析Json和XML之前,我們先來搞清楚兩個概念:序列化反序列化

      「序列化,即Serialization,是一個將對象的狀態信息轉變為可以存儲或傳輸的形式的過程。」

    「反序列化,即Deserialization,顧名思義是一個將可以存儲或傳輸的序列轉變為某個對象的狀態信息的過程。」

  從上面的定義,我們可以得出兩個結論:

  ① 這兩個過程互為逆過程;

  ② 無論是序列化還是反序列化,對象的狀態信息都與一段序列相對應。

  下面我們就來看看如何用C#語言進行Json和XML的序列化和反序列化。

  

   Json的序列化與反序列化

   ① 使用Newtonsoft.Json庫

  首先我們先來看一段代碼:

 1 public void JsonDeserialize(string jsonString)
 2 {
 3     //Json的反序列化操作
 4     JObject jObject = JsonConvert.DeserializeObject(jsonString) as JObject;
 5     //清空listBox的結果
 6     listBoxResult.Items.Clear();
 7     //將解析得到的數據顯示到listBox中
 8     listBoxResult.Items.Add("Name : " + jObject["name"] ?? "null");
 9     listBoxResult.Items.Add("Doing : " + jObject["doing"] ?? "null");
10     listBoxResult.Items.Add("HandleContent : " + jObject["handleContent"] ?? "null");
11     listBoxResult.Items.Add("DateTime : " + jObject["dateTime"] ?? "null");
12 }

  上面這個方法的作用是,接收一段Json序列作為參數,在完成反序列化后,將所有字段的值輸出到ListBox中,下面是簡單的測試:

  

  可以看到,僅僅用了一行代碼,Json的內容就按照字段全都被獲取到了,下面就來好好了結一下Newtonsoft.Json這個庫的用法吧。

  Newtonsoft.Json是.NET平台中一個非常流行且高性能的Json序列化和反序列化開源庫,提供了許多用於序列化和反序列化Json的類及方法,除此之外它還提供了用Linq操作Json、將Json轉換成XML的功能,相較於傳統的用於序列化和反序列化的DataContractJsonSerializer類更好用,更高效。本文只介紹其中比較重要的JsonConvert類和幾個常用的類型,更多內容大家可以訪問Json.NET的主頁進行了解。

  在介紹使用方法前,先來說說Newtonsoft.Json.dll的引入。從VS2013開始,ASP.NET的項目均自帶了Newtonsoft.Json.dll,直接使用即可,而其他版本或者其他項目如果沒有的話,可以進入其官方主頁,找到Github托管的地址進入並下載。下面演示引用該動態鏈接庫的流程,

  ① 點擊菜單欄的項目->添加引用,或者在解決方案資源管理器中找到項目,右鍵引用屬性,點擊添加引用,然后就會打開該窗口。

  該窗口打開后,如果是第一次添加Newtonsoft.Json.dll,請找到右下方的瀏覽按鈕,並選擇Newtonsoft.Json.dll;若以前添加過該庫,可以點擊左側的瀏覽菜單,並點擊最近,右側會顯示以前添加過的庫的信息,勾上需要的庫,最后點擊右下方的確定即可。

  

  

  添加了動態鏈接庫還沒完,要想使用其中的類和方法,需要引入相應的命名空間(如果你不想每定義一個對象就寫一長串命名空間的話),本文主要用到的命名空間是Newtonsoft.Json以及Newtonsoft.Json.Linq

  

  完成這兩步之后就可以舒舒服服的開始解析我們偉大的Json了。

  Now,先來介紹本庫比較重要的類,JsonConvert。JsonConvert是個非常實用的工具類,它提供了用於反序列化的DeserializeObject方法和DeserializeObject<T>方法,以及用於序列化的SerializeObject方法。由於這些方法都是靜態方法,因此無需創建JsonConvert對象,直接使用妥妥的(事實上JsonConvert是個靜態類,根本無法被實例化)。

  在最開始演示的例子中,我們使用了JsonConvert.DeserializeObject方法,這是最原始的Json反序列化方法,它接收string類型的參數並返回object類型的對象。那么你也許會問到,它返回的對象究竟是什么類型???想要真正獲得該對象的信息,就必須將其強制轉換成具體的派生類。那么,我們可以使用GetType方法來一探究竟。

1 private void btnDeserialize_Click(object sender, EventArgs e)
2 {
3     //JsonDeserialize(textBox1.Text);
4     var secretObject = JsonConvert.DeserializeObject(textBox1.Text);
5     MessageBox.Show(secretObject.GetType().ToString());
6 }

   我們先在一個按鈕的點擊事件對應的方法中解析輸入的Json序列,由於我們還不確定JsonConvert.DeserializeObject方法返回什么對象,因此我們先用隱式類型對象去引用該方法返回的結果,然后調用其GetType方法,獲取該對象所屬類型的元數據,最后將對象的完整類型名顯示在對話框當中。執行這段代碼,你將會看到如下結果:

  

  也許這時候在你的心里又有一萬個草泥馬呼嘯而過,“What?還沒搞清楚JsonConvert類的用法,怎么又跑出了一個奇怪的類?”

  但是沒關系,接下來我會對大家的謎團進行逐一的解釋。

   在Newtonsoft.Json.Linq命名空間中,定義了一些用於表示不同形式的Json字符串的類,它們包括JObject、JProperty、JToken、JArray、JValue、JContainer等等。

        那么在這些類當中,JToken充當着標桿的角色,它是一個抽象類,並實現了IJEnumerable<T>接口,而IJEnumerable<T>接口繼承自IEnumerable<T>接口,這意味着所有繼承JToken的類都可以使用foreach語句進行遍歷,而接下來要介紹的這些類,全都派生自JToken。另外,JToken還定義了First、Last、Parent、Root、Item、Previous、Next等遍歷經常會用到的屬性,因此在使用這些類的時候,遍歷、查找具體類等操作就顯得非常的簡單。說了這么多,是時候說說JObject了,這是個JToken的派生類,同時也是實現了JContainer接口、ICollection<T>、IKeyValuePair<T>,這意味着JObject可以存儲多個繼承JToken類型的對象,同時也可以遍歷它們,還可以用鍵去獲取相應的值,也許你還不清楚這些有什么用,那么我們先來了解一下Json的基本結構:

  一般來說,被大括號括起來的內容可以看成是一個整體的(一個對象的),而被中括號括起來的內容,可以看成是一組內容(一個數組),在一個復雜的Json結構中,大括號里面的某個鍵的值可以在嵌一個大括號,代表該屬性值為另一個Json對象,同理一個大括號中的某個鍵的值可以嵌一個中括號,代表該屬性值是一個數組,如

 1 //對於這種Json來說,可以看成是一個對象,里面包含了name、doing、handleContent和dateTime屬性。
 2 {
 3     "name":".NET Coder",   
 4     "doing":"Deserialization",
 5     "handleContent":"Json",
 6     "dateTime":"2017-09-11 12:12:12"
 7 }
 8 
 9 //對於這種Json來說,可以看成是一個數組對象,里面包含了兩個對象。
10 [
11   {
12     "name":".NET Coder",   
13     "doing":"Deserialization",
14     "handleContent":"Json",
15     "dateTime":"2017-09-11 12:12:12"
16   },
17   {
18     "name":"Java Coder",   
19     "doing":"Deserialization",
20     "handleContent":"Json",
21     "dateTime":"2017-09-22 21:21:21"
22   }
23 ]

  在上一個演示的例子中,我們用GetType方法去判斷完成反序列化之后,實際返回的類型是什么;那么接下來我們就將上面這兩種不同結構的Json序列放入程序中解析,看看得到的對象是不是都是JObject。

  

      成功得到了JObject類型的對象。

  

  這次我們竟然得到了JArray?!

  這下我們也許就明白了,JObject用於表示一個完整的Json對象結構,而JArray用於表示一個Json數組,其中會包括一個或多個Json對象。

  在獲得JObject對象之后,我們可以用屬性名(鍵)去獲得相應的屬性值,也可以使用Next、Previous等屬性去逐個訪問JObject的屬性。

  而獲得JArray對象之后,我們可以使用for或者foreach語句去遍歷該對象內的元素,然后根據某個屬性名或Next、Previous等屬性去訪問當前元素某個屬性的屬性值。

  那么JProperty和JValue類型又有什么作用呢?

  我們可以來調試一下程序,並觀察JObject對象內的結構,

  如圖我們在使用foreach循環遍歷JObject內部的屬性,可以看到,First屬性指向了JObject的第一個屬性,而它的類型是JProperty,由此我們可以知道,JProperty類用於描述一個Json對象中的某一個屬性,在其內部包含了一個KeyValuePair<string, JToken>的成員變量,用於存儲該Json屬性的鍵值對,而這個值,其實就是JValue類型的變量。

  

  那么到這里為止,我們已經掌握了DeserializeObject方法的用法了,但是很明顯,這種方法解析Json數據實在太麻煩了,我們需要把所有Json對象的屬性一個個獲取到,然后才能得到其中的值,有沒有更簡介的辦法呢?

  有!有!有!重要的事情說三遍。

  下面該輪到DeserializeObject<T>方法出場了,這是個泛型方法,指定T的類型並傳入Json字符串作為參數后,該方法會完成反序列化並返回一個T類型的對象。那么我們該指定什么類型給該方法呢?這個類型需要根據Json數據的層次關系自行設計,如果大家是第一次使用,不知道該如何設計,那么我們來讓VS幫我們設計,下面給大家介紹一個神奇的功能,VS的自動建類功能:

  還是這段Json序列,假設我們需要將其反序列化,現在卻不知道怎么設計類,那么我們可以先復制這段Json序列,

1 {
2     "name":".NET Coder",   
3     "doing":"Serialization",
4     "handleContent":"Json",
5     "dateTime":"2017-09-11 12:12:12"
6 }

  打開VS,在菜單欄找到編輯->選擇性粘貼,點擊將Json粘貼為類,就會出現下面的結果,

  原本空白的地方,突然出現了一個類的定義(如果Json有多個層級,VS會幫我們生成多個類),這個類清晰的反映了這段Json的結構,此時我們只需要將這個自動生成的類,用到方法里就好了:

  

   

  我們改寫了JsonDeserialize方法,並使用了由VS生成的類,可以看到我們不需要再根據屬性名去獲得屬性值了,也不用再擔心忘記有什么屬性或者屬性名寫錯,只需要在對象后面加上一點,該對象的所有屬性都會出現在選擇列表中。是不是突然覺得眼前一亮,一身輕松了呢?但其實這個做法隱藏了一點點小問題,因為VS是根據Json原來的屬性名來定義屬性的,這就要求Json的屬性名遵循C#的標識符定義規則,然后事實上,在Json中可能會出現不符合C#標識符定義規范的屬性名,這並沒有錯,如下面這段Json:

1 {
2     "name":"money",   
3     "price":"66.6",
4     "int":"64",
5     "a-b":"c-d",
6     "@#$":"sdfsdf"
7 }

  可以看到,雖然VS會用_去代替不合法的字符串並按照規范生成屬性名,但是這樣子的屬性名已經失去了它的意義,我們無法得知這個屬性表達了什么含義,為了解決這個問題,我們可以使用C#的注解來給該類的屬性制定數據協定

  

 

   DataContract和DataMember注解來自於命名空間System.Runtime.Serialization,這個命名空間所在的動態鏈接庫項目默認是沒有引入的,需要手動引入,我們只需要按照上面引入Newtonsoft.Json.dll的方式引入就好,但是需要注意的是,System.Runtime.Serialization所在的動態鏈接庫在VS程序集中是提供了的,我們只需要在添加引用界面,點擊程序集,並在右側的列表找到System.Runtime.Serialization並勾選,然后點擊右下角的確定按鈕即可。

  完成這些步驟之后我們就可以改造Json的實體類,為其添加數據協定。什么是數據協定呢,即數據的發送方和接收方不需要使用相同的類型,只需要遵循數據協定即可。為了使該類能夠添加數據協定,需要在類名上方添加[DataContract]注解,說明該類的屬性使用了數據協定,並且允許將該類進行序列化,如果不加這個注解,就不能使用[DataMember]注解了。添加[DataContract]注解后,我們就可以在對應的屬性上添加[DataMember]注解,該注解有4個參數:

參數名 作用
Name 標識該屬性對應的Json屬性名
Order 標識該屬性序列化和反序列化的順序
IsRequired 標識該屬性在讀取或反序列化時是否必須存在,類型為bool
EmitDefaultValue 標識在序列化的過程中是否使用該屬性的默認值來序列化

 

  上圖我們使用了Name參數來設置每個屬性對應的Json屬性名,從而增強了該類在程序中的可讀性,同時又不會對序列化和反序列化的過程產生麻煩。下面再改寫一下JsonDeserialize方法吧:

  

   

  測試成功!!!

   另外再補充一個小知識,當我們需要反序列化很多簡單的Json時,為了避免創建過多的實體類,我們可以使用dynamic動態類型來代替自定義的實體類,在這里我們改寫上述例子,使用dynamic類型來實現相應功能:

  

 

  在這個例子中,我沒有定義新的類,而是使用了動態類型,這種類型的對象有一個特點:運行時檢查。在使用這種對象的時候我們要注意,無論是使用它的屬性,還是調用它的方法,編譯器都不會對其進行編譯時檢查,也就是說我們必須保證屬性名或者方法名沒有寫錯,否則會出現表達式為空或者引發異常的現象。

  

   

  調用不存在的屬性返回Null。

 

  

  調用不存在的方法引發異常。

 

  

  在沒有錯誤的情況下,成功運行!

  

  到這里反序列化的介紹算是告一段落了,接下來我們講講使用JsonConvert.SerializeObject方法完成序列化操作。

  前面那么多操作的目的是為了解析Json字符串,讓我們能方便的獲得其中的信息,但是如果我們不是信息的接收方而是發送方呢,我們要怎么構造Json字符串?顯然不可能用字符串拼接的方法,效率低下,難以排錯。因此今天我們就要用一行代碼來完成這件事情。

  

  

 

  沒錯,就只有一行代碼!!!

 

  

  

  看吧,是不是一行代碼就讓一個活生生的對象瞬間變成了一串Json了呢!!!

  

  ② 使用DataContractJsonSerializer類

  其實本人並不想講解這個類,第一使用過程非常麻煩,第二效率低下,實際工作中也不會用它,但是從學習的角度我們還是要了解一下這個類的存在,並且這個類序列化和反序列化Json的過程和XML的序列化和反序列化很相似,因此還是有必要讓大家了解一下。

  在此不多說其他廢話,直接上代碼:

  DataContractJsonSerializer序列化

 1 public void OldJsonSerialize()
 2 {
 3     DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(HandleObject));
 4     //創建實體類
 5     HandleObject jObject = new HandleObject() {
 6         Name = ".NET Coder",
 7         Doing = "Serialization",
 8         HandleContent = "Json",
 9         DateTime = DateTime.Now.ToString("yyyy-MM-ss")
10     };
11     //創建內存流,用於存儲序列化后得到的內容
12     MemoryStream ms = new MemoryStream();
13     //將對象序列化后存入內存流
14     serializer.WriteObject(ms, jObject);
15     //將內存流的內容轉換成字符串並輸出到文本框
16     textBox1.Text = Encoding.UTF8.GetString(ms.ToArray());
17 }

 

  DataContractJsonSerializer反序列化

 1 public void OldJsonDeserialize()
 2 {
 3     DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(HandleObject));
 4     //將Json字符串轉換成字節數組
 5     byte[] content = Encoding.UTF8.GetBytes(textBox1.Text);
 6     //用字節數組初始化一個內存流對象
 7     MemoryStream ms = new MemoryStream(content);
 8     //完成反序列化操作
 9     HandleObject handleObject = serializer.ReadObject(ms) as HandleObject;
10     //將解析得到的數據顯示到listBox中
11     listBoxResult.Items.Add("Name : " + handleObject.Name ?? "null");
12     listBoxResult.Items.Add("Doing : " + handleObject.Doing ?? "null");
13     listBoxResult.Items.Add("HandleContent : " + handleObject.HandleContent ?? "null");
14     listBoxResult.Items.Add("DateTime : " + (handleObject.DateTime.ToString() ?? "null"));
15 }

 

  DataContractJsonSerializer類和JsonConvert類最大的不同就在於,DataContractJsonSerializer類將操作流的細節暴露了出來,需要由用戶自行完成,相比JsonConvert直接使用字符串要麻煩不少。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM