使用 Json.NET完成 .NET 對象的序列化和反序列化,以及對復雜 Json 數據的解析。
前言
最近在 C# 項目中需要使用到 Json 格式的數據,我簡單上網搜索了一下,基本上有兩種操作 Json 數據的方法:
- 使用 Windows 系統自帶的類
- 使用第三方的包
本着“第三方包一定有比系統自帶類優秀地方,否則就不會存在”的原則,再加上 JavaScriptSerializer、DataContractJsonSerializer等這些自帶類庫使用起來很麻煩,我毫不猶豫地就選擇了在 Json 操作方面小有名氣的 Json.NET。Json.NET 自己也做了與自帶類庫的比較,詳情可以見 Json.NET vs .NET Serializers和 Json.NET vs Windows.Data.Json。
Json.NET 是一個 Newtonsoft 編寫的開源類庫包,你可以直接到 Github 上查看項目的源碼和說明。Json.NET 提供了大量對於 Json 數據的操作方法,他們使用起來非常簡單,而且執行效率很高。
.NET 對象的序列化和反序列化
1. 普通對象的序列化和反序列化
JsonConvert是 Json.NET 中的一個數據轉換類,提供了用於 .NET 對象序列化和反序列化的方法 SerializeObject()和 DeserializeObject()。在通常情況下,我們也只需要使用這兩個方法就足以完成任務了。
比如說,我們現在定義了一個學生類 Student:
class Student //學生類 { public int Id { get; set;} //學號 public string Name { get; set;} //姓名 public double[] Scores { get; set;} //成績 }
現在我們創建一個學生類對象,並使用 JsonConvert類提供的 SerializeObject()方法將它轉換到 Json 字符串(需要引入命名空間 using Newtonsoft.Json):
Student student = new Student { Id = 12883, Name = "Jim David", Scores = new double[] { 87.5, 92, 76.2 } }; string jsonStudent = JsonConvert.SerializeObject(student); //{"Id":12883,"Name":"Jim David","Scores":[87.5,92.0,76.2]}
可以看到在序列化的過程中,JsonConvert會將 .NET 對象中的變量名轉換為 Json 中的“屬性”,同時將變量的值復制為 Json 的“屬性值”。接下來,我們嘗試將 Json 字符串轉換為 Student對象,使用 JsonConvert提供的 DeserializeObject()方法:
Student deserializedStudent = JsonConvert.DeserializeObject<Student>(jsonStudent); Console.WriteLine("student.Id = " + deserializedStudent.Id); //student.Id = 12883 Console.WriteLine("student.Name = " + deserializedStudent.Name); //student.Name = Jim David
可以看到,創建的學生對象 student的 Json 字符串被順利地解析成了 Student 對象。在調用 DeserializeObject()方法進行反序列化時,最好使用帶泛型參數的重載方法。
如果在調用 DeserializeObject()時不指定對象類型,JsonConvert會默認轉換為 Object 對象。
2. 集合的序列化和反序列化
上面我們已經簡單測試了 JsonConvert 提供的 SerializeObject()和 DeserializeObject()方法,完成了 .NET 對象的序列化和反序列化。
C# 項目中,除了自定義的類型外,集合(Collections)也是經常會使用的數據類型,包括列表、數組、字典或者我們自定義的集合類型。我們同樣可以使用之前使用的 SerializeObject()和 DeserializeObject()方法來完成集合的序列化和反序列化。
為了使轉換后的結果更加易讀,我指定轉換后的 Json 字符串帶縮進。通過向 SerializeObject()方法傳遞進第二個參數 Formatting 實現。
Student student1 = new Student { Id = 12883, Name = "Jim David", Scores = new double[] { 87.5, 92, 76.2 } }; Student student2 = new Student { Id = 35228, Name = "Milly Smith", Scores = new double[] { 92.5, 88, 85.7 } }; List<Student> students = new List<Student>(); students.Add(student1); students.Add(student2); string jsonStudents = JsonConvert.SerializeObject(students, Formatting.Indented); //[ // { // "Id": 12883, // "Name": "Jim David", // "Scores": [ // 87.5, // 92.0, // 76.2 // ] // }, // { // "Id": 35228, // "Name": "Milly Smith", // "Scores": [ // 92.5, // 88.0, // 85.7 // ] // } //]
接下來我們對上面生成的 Json 字符串進行反序列化,解析出原有的 Student 類型列表。同樣,我們需要使用帶泛型參數的 DeserializeObject()方法,指定 JsonConvert 解析的目標類型。
string jsonStudentList = @"[ { 'Id': 12883, 'Name': 'Jim David', 'Scores': [ 87.5, 92.0, 76.2 ] }, { 'Id': 35228, 'Name': 'Milly Smith', 'Scores': [ 92.5, 88.0, 85.7 ] } ]"; List<Student> studentsList = JsonConvert.DeserializeObject<List<Student>>(jsonStudentList); Console.WriteLine(studentsList.Count); //2 Student s = studentsList[0]; Console.WriteLine(s.Name); //Jim David
如果 Json 對象擁有統一類型的屬性和屬性值,我們還可以把 Json 字符串反序列化為 .NET 中的字典,Json 對象的“屬性”和“屬性值”會依次賦值給字典中的 Key 和 Value。下面我舉一個簡單的例子:
string json = @"{""English"":88.2,""Math"":96.9}";
Dictionary<string, double> values = JsonConvert.DeserializeObject<Dictionary<string, double>>(json);
Console.WriteLine(values.Count);
//2
Console.WriteLine(values["Math"]);
//96.9
解析復雜的 Json 字符串
如今大量的 Web API 為我們編寫復雜程序提供了極大的方便,例如百度地圖 API、圖靈機器人 API等等,利用這些 Web 應用程序我們可以充分發揮雲服務的優勢,開發出大量有趣的應用。
Web API 通常返回 Json 或 XML 格式的檢索數據,由於 Json 數據量更小,所以目前大多數情況下我們都選擇返回 Json 格式的數據。
如果返回的 Json 文檔很大,而我們僅僅需要其中的一小部分數據。按照之前的方法,我們必須首先定義一個與 Json 對象對應的 .NET 對象,然后反序列化,最后才能從對象中取出我們需要的數據。而有了 Json.NET,這個任務就很容易實現了,我們可以局部地解析一個 Json 對象。
下面以獲取 Google 搜索結果為例,簡單演示一下對復雜結構 Json 文檔的解析。
string googleSearchText = @"{ 'responseData': { 'results': [ { 'GsearchResultClass': 'GwebSearch', 'unescapedUrl': 'http://en.wikipedia.org/wiki/Paris_Hilton', 'url': 'http://en.wikipedia.org/wiki/Paris_Hilton', 'visibleUrl': 'en.wikipedia.org', 'cacheUrl': 'http://www.google.com/search?q=cache:TwrPfhd22hYJ:en.wikipedia.org', 'title': '<b>Paris Hilton</b> - Wikipedia, the free encyclopedia', 'titleNoFormatting': 'Paris Hilton - Wikipedia, the free encyclopedia', 'content': '[1] In 2006, she released her debut album...' }, { 'GsearchResultClass': 'GwebSearch', 'unescapedUrl': 'http://www.imdb.com/name/nm0385296/', 'url': 'http://www.imdb.com/name/nm0385296/', 'visibleUrl': 'www.imdb.com', 'cacheUrl': 'http://www.google.com/search?q=cache:1i34KkqnsooJ:www.imdb.com', 'title': '<b>Paris Hilton</b>', 'titleNoFormatting': 'Paris Hilton', 'content': 'Self: Zoolander. Socialite <b>Paris Hilton</b>...' } ], 'cursor': { 'pages': [ { 'start': '0', 'label': 1 }, { 'start': '4', 'label': 2 }, { 'start': '8', 'label': 3 }, { 'start': '12', 'label': 4 } ], 'estimatedResultCount': '59600000', 'currentPageIndex': 0, 'moreResultsUrl': 'http://www.google.com/search?oe=utf8&ie=utf8...' } }, 'responseDetails': null, 'responseStatus': 200 }";
上面就是從 Google 搜索返回的 Json 數據,我們現在需要的是 responseData屬性下的 results列表中結果,而且僅僅需要結果中的 title
、content
和 url
屬性值。
public class SearchResult { public string Title { get; set; } public string Content { get; set; } public string Url { get; set; } }
//將 Json 文檔解析為 JObject JObject googleSearch = JObject.Parse(googleSearchText); //將獲得的 Json 結果轉換為列表 IList<JToken> results = googleSearch["responseData"]["results"].Children().ToList(); //將 Json 結果反序列化為 .NET 對象 IList<SearchResult> searchResults = new List<SearchResult>(); foreach(JToken result in results) { SearchResult searchResult = JsonConvert.DeserializeObject<SearchResult>(result.ToString()); searchResults.Add(searchResult); } // Title = <b>Paris Hilton</b> - Wikipedia, the free encyclopedia // Content = [1] In 2006, she released her debut album... // Url = http://en.wikipedia.org/wiki/Paris_Hilton // Title = <b>Paris Hilton</b> // Content = Self: Zoolander. Socialite <b>Paris Hilton</b>... // Url = http://www.imdb.com/name/nm0385296/
可以看到,對 Json 文檔的解析基本分為以下幾步:
- 將 Json 文檔轉換為 JObject 對象
- 使用
JObject[屬性]
獲取 JObject 對象中某個屬性的值(JToken 格式)
可以繼續通過JToken[屬性]
獲取屬性內部的屬性值(依然為 JToken 格式) - 將 JToken 格式的屬性值反序列化為 .NET 對象
如果屬性值為我們需要的數據對象,那么直接反序列化該對象就可以了;如果屬性值為列表(比如上面 results屬性的值),那么就可以調用 JToken 類的 Children()函數,獲得一個可迭代的 JEnumerable<JToken> 對象(用於迭代訪問列表中的每一個對象),最后再依次反序列化列表中的對象。