Net深入實戰系列—JSON序列化那點事兒
序
當前主流的序列化JSON字串主要有兩種方式:JavaScriptSerializer及Json.net(Nuget標識:Newtonsoft.Json)。JavaScriptSerializer是微軟官方提供的一種方法,所以如果你用的是asp.net mvc,在Action中如果你返回的語句寫的是”return Json(xxx);“,其實你用的就是JavaScriptSerializer方式。現在更多的人選擇的是Json.net,因為它為使用者提供了更加清晰地使用體驗,清晰在哪?本文主要就是帶你走進它們的世界。
JavaScriptSerializer與Json.net
序列化
我們先定義一個測試用的簡單類--Person:
- publicclass Person
- {
- publicstring Name;
- publicint Age;
- public Guid TokenId;
- public DateTime RegTime;
- public Person Child;
- public Person Friend;
- }
類中的成員僅用來區分不同的變數型別。我們分別以JavaScriptSerializer和Json.net來序列化:
-
var person =
- {
- Age = 28,
- Name = "李玉寶<yubaolee:>",//故意新增特殊字元
- RegTime = DateTime.Now,
- TokenId = Guid.NewGuid(),
- Child = new Person
- {
- Age = 1,
- Name = "baby",
- RegTime = DateTime.Now,
- TokenId = Guid.NewGuid()
- }
-
}; //注意這裡面的Friend沒有賦值,預設為空
- JavaScriptSerializer serializer = new JavaScriptSerializer();
- var jsstr = serializer.Serialize(person); //使用JavaScriptSerializer序列化
- string newtonstr = JsonConvert.SerializeObject(person); //使用Json.net序列化
JavaScriptSerializer序列化是先生成一個物件,然後呼叫它的成員函式Serialize進行序列化;
Json.net直接使用提供的靜態成員JsonConvert.SerializeObject進行序列化;
兩者使用都比較簡單,Json.net呼叫起來方便那麼一丟丟!我們來看一下控制檯輸出結果:
上面綠色為JavaScriptSerializer的結果,下面黃色背景為Json.net的結果,這裡需要注意幾個地方:
1、 JavaScriptSerializer序列化後的時間格式:"\/Date(1441813200214)\/" 表示的是1970年1月1日(DateTime的最小值)到date實際表示的日期之差的總毫秒數。通常我們需要把它轉成標準的時間格式。可以用下面的方法進行字串處理:
- jsstr = Regex.Replace(jsstr, @"\\/Date\((\d+)\)\\/", match =>
- {
- DateTime dt = new DateTime(1970, 1, 1);
- dt = dt.AddMilliseconds(long.Parse(match.Groups[1].Value));
- dt = dt.ToLocalTime();
- return dt.ToString("yyyy-MM-dd HH:mm:ss");
- });
處理完成後的效果:
當然,你還可以通過使用繼承JavaScriptConverter的方式,下面反序列化中會具體提及到這種方式。
Json.net預設生成的日期也不方便客戶端閱讀,需要簡單的處理一下:
- string newtonstr = JsonConvert.SerializeObject(p, Formatting.Indented,
- new IsoDateTimeConverter() {DateTimeFormat = "yyyy-MM-dd HH:mm:ss"});
2、JavaScriptSerializer序列化會對特殊字元(如<>等)進行編碼,比如上面的\u003c \u003e,很多人看到這個的時候,第一感覺就是太扯蛋了,接下來就是各種百度,怎麼把這個轉成正常的”<>”。實際上你不用做任何操作,這是標準的JS編碼方式,前端會自行處理這個問題。比如:
- <script type="text/javascript">
- var str = 'yubaolee <yubaolee>'
- var str2 = 'yubaolee \u003cyubaolee\u003e';
- alert(str == str2); //結果為true
- </script>
附:如果你真的不明白\u003c這到底是個什麼玩意,請移步:字元編碼。
從上面兩點可以看出,JavaScriptSerializer序列化出來的JSON字串容易給人造成一些困惑,而Json.net完全沒有上面的兩種情況處理。所以現在很多人都在用Json.net,但從Html標準的角度上來看,JavaScriptSerializer序列化出來的結果更符合Html的要求。不過為了操作習慣,還是建議使用Json.net。
反序列化
我們分別用兩種方式對上面已經成功序列化的兩個字串進行反序列化:
- //對JavaScriptSerializer生成的字串進行反序列化
- //使用JavaScriptSerializer方式
- var jsperson = serializer.Deserialize<Person>(jsstr);
- //使用Json.net方式
- var newtonperson = JsonConvert.DeserializeObject<Person>(jsstr);
- //對Json.net生成的字串進行反序列化
- var jsperson2 = serializer.Deserialize<Person>(newtonstr);
- var newtonperson2 = JsonConvert.DeserializeObject<Person>(newtonstr);
通過執行會發現4個反序列化程式碼都能正常執行,而不是像以前某些前輩說的,JavaScriptSerializer序列化的串只能用它反序列化,Json.net序列化的串只能用Json.net來反序列化。
上面反序列化的字串是程式生成的,能正常反序列化不足為奇。但通常我們要反序列化的字串是客戶提交到伺服器上面來的串,他們通常是不完整的,或有些還會出現型別不符的情況。比如:
- string noChildStr =
- "{\"Name\":\"李玉寶<yubaolee:>\"," +
- "\"Age\":28," +
- "\"RegTime\":\"2015-09-11 00:10:48\"," +
- "\"Friend\":null}";
- var jsperson = new JavaScriptSerializer().Deserialize<Person>(noChildStr);
- var newtonperson = JsonConvert.DeserializeObject<Person>(noChildStr);
注意這個字串中,沒有TokenId,沒有Child,而且Friend為null。看一看結果:
兩個都很智慧嘛!解析的結果全部是我們想要的內容。但如果像下面這樣呢?
- string noChildStr =
- "{\"Name\":\"李玉寶<yubaolee:>\"," +
- "\"Age\":28," +
- "\"RegTime\":\"2015-09-11 00:10:48\"," +
- "\"Friend\":null," +
- "\"TokenId\":null}"; //注意這個TokenId為空
在執行的時候,程式會直接報錯。
錯誤的內容很容易理解,因為我們把一個null賦值給Guid型別,肯定會出錯。在實際的專案操作過程中還有可能出現把一個內容為空的字串傳給Datetime型別,把一個數字傳給GUID等各種引數傳遞的問題,關鍵是我們還要來處理它們,而不能使程式直接報錯崩潰。
1、在JavaScriptSerializer中有一個JavaScriptConverter可以來處理這些問題,它是用來實現JSON序列化中自定義型別的處理。比如下面的程式碼,就是處理把一個null賦值給Guid的情況:
- publicclass PersonJsConverter : JavaScriptConverter
- {
- publicoverrideobject Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
- {
- Person person = new Person();
- object value = null;
- if (dictionary.TryGetValue("TokenId", out value) && value != null)
- person.TokenId = (Guid) value;
- //其他欄位略...
- return person;
- }
- publicoverride IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
- {
- Dictionary<string, object> dic = new Dictionary<string, object>();
- var node = obj as Person;
- if (node == null)
- returnnull;
- if (!string.IsNullOrEmpty(node.Name))
- dic.Add("Name", node.Name);
- //其他欄位略...
- return dic;
- }
- publicoverride IEnumerable<Type> SupportedTypes
- {
- get
- {
- returnnew Type[] { typeof(Person) };
- }
- }
- }
然後在反序列化之前,我們把這個轉換註冊到實體物件中,這時再執行,程式就一切正常了:
- JavaScriptSerializer serializer = new JavaScriptSerializer();
- serializer.RegisterConverters(new JavaScriptConverter[] { new PersonJsConverter(), });
- var deserialize = serializer.Deserialize<Person>(noChildStr);
2、在使用Json.net時,採用了一種更加優雅的方式來處理這個問題--JsonConverter,它可以單獨處理一個指定的類成員變數。這樣就不用像上面的JavaScriptConverter一樣處理整個類的所有成員。程式碼如下:
- publicclass GuidConverter : JsonConverter
- {
- publicoverridebool CanConvert(Type objectType)
- {
- return objectType.IsAssignableFrom(typeof(Guid));
- }
- publicoverrideobject ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
- {
- try
- {
- return Guid.Parse(reader.Value.ToString());
- }
- catch
- {
- //如果傳進來的值造成異常,則賦值一個初值
- return Guid.Empty;
- }
- }
- publicoverride