.net本身除了支持SOAP、XML、二進制等序列化和反序列化,后來也加入了對JSON的序列化的支持。然而,在實際開發中,常常會遇到結構不確定的JSON對象,這些對象可能是其他代碼動態生成的,你事先無法估計它的結構,甚至它的字段名字是動態改變的。
這種情況下,我們很難用一個固定的類來進行反序列化,后來我嘗試過從DynamicObject類派生出一個自定義的動態類型,希望通過這種方法能夠將動態生成的JSON讀出來,但結果依舊不可;后來我又實現了ISerializable接口,想着自行去控制一下數據的讀取,但仍然未果。
最終我總結出來,只有下面這個方法比較省事,並且可以做到將動態的JSON進行反序列化。
做ASP.NET開發的朋友應該會熟悉一個類——位於System.Web.Script.Serialization命名空間下的JavaScriptSerializer類。因為這個類是為Web開發服務的,其實可以用於整個.net框架,即你在WinForm、WPF等程序中依舊可以用。這個類的作用是將指定的JSON字符串進行序列化和反序列化,參與操作的類型可以是固定的,如果JSON是固定結構的,這樣就可行。而對於結構不固定的JSON,這個類可以以字典的形式進行操作,即調用DeserializeObject方法后會返回一個Object類型的對象,實際上這個對象是實現了IDictionary<string, object>接口的,這樣一來,反序列化的結果就可以作為字典來操作。如果JSON里面有嵌套的對象,則返回的字典對象中會嵌套着字典對象。
於是,我就寫了這么一個類:
public sealed class JsonObjectReader { private string innerJson = null; public JsonObjectReader(string json) { innerJson = json; } public dynamic GetObject() { dynamic d = new ExpandoObject(); // 將JSON字符串反序列化 JavaScriptSerializer s = new JavaScriptSerializer(); object resobj = s.DeserializeObject(this.innerJson); // 拷貝數據 IDictionary<string, object> dic = (IDictionary<string, object>)resobj; IDictionary<string, object> dicdyn = (IDictionary<string, object>)d; foreach (var item in dic) { dicdyn.Add(item.Key, item.Value); } return d; } }
有人會問我,GetObject方法為什么要返回動態類型?是為了方便操作,ExpandoObject是一種簡單易用並且現成的動態類型,在C#中聲明變量時應用上dynamic關鍵字,告訴編譯器這家伙是動態類型,在編譯檢查時可以“網開一面”。而且,我發現ExpandoObject類是顯式實現了IDictionary<string, object>接口的,說明你還可以把它強制轉換為字典數據來操作。
這種做法一舉兩得,如果方便使用,就當成動態對象來訪問,在不方便使用時,也可以當作字典數據來用。
下面舉一個不方便使用動態訪問的例子:
string json = "{" + "\"0592\" : \"廈門市\"," + "\"0351\" : \"太原市\"," + "\"0411\" : \"大連市\"," + "\"0459\" : \"大慶市\"" + "}"; JsonObjectReader rd = new JsonObjectReader(json); dynamic res = rd.GetObject(); IDictionary<string, object> d = (IDictionary<string, object>)res; foreach (var item in d) { Console.WriteLine($"{item.Key} = {item.Value}"); }
大伙會發現,這個JSON你是很難用常規方法進行反序列化的,因為它的字段是城市的區號,是不固定的,在聲明類時你無法事先確定類的屬性或字段成員。同時你也發現,字段名是數字的,就算以動態對象得到結果,你也不能以obj.0459這樣的語法來訪問,因為標識符是不能由數字開頭的。這種情況下不能用動態對象來訪問,但可以把它轉換為字典對象來處理。
得到結果如下圖。
但是,下面這種用法,因為JSON的字段名不是數字開頭,所以能夠以動態對象的方式訪問。
json = "{\"Name\":\"小明\", \"Age\":25, \"Email\":\"abcd@dog.cc\"}"; JsonObjectReader rd2 = new JsonObjectReader(json); dynamic res2 = rd2.GetObject(); Console.WriteLine($"姓名:{res2.Name}"); Console.WriteLine($"年齡:{res2.Age}"); Console.WriteLine($"電郵:{res2.Email}");
因為Name、Age、Email這些字段不是數字開頭,符號標識符的規范要求,所以后面可以用res2.Name的方式來訪問,就像訪問普通對象實例一樣。
得到的結果如下圖。
最后要說明一下的是,這種方法只用於.NET框架的應用程序,如ASP.NET、WPF等。如果是Windows Store App的話,可以使用RT API中的JSON相關的類來處理,這些類都位於Windows.Data.Json命名空間。