本文轉載自 http://xiaosheng.me/2016/10/01/article25/
最近在 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 對象。
集合的序列化和反序列化
上面我們已經簡單測試了 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> 對象(用於迭代訪問列表中的每一個對象),最后再依次反序列化列表中的對象。