想想某一天,你在看LOL攻略的時候,系統突然崩潰了,接著瀏覽器出現了密密麻麻的LOL帳號和密碼,你一定在想:“天啊,這次要發財了,說不定裡面有超凡號或者王者號,我得趕緊全部記下來。”然而說完你就驚呆了,那麼多的帳號密碼,而且全部寫在了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直接使用字元串要麻煩不少。