JSON序列化那點事兒
序
當前主流的序列化JSON字符串主要有兩種方式:JavaScriptSerializer及Json.net(Nuget標識:Newtonsoft.Json)。JavaScriptSerializer是微軟官方提供的一種方法,所以如果你用的是asp.net mvc,在Action中如果你返回的語句寫的是”return Json(xxx);“,其實你用的就是JavaScriptSerializer方式。現在更多的人選擇的是Json.net,因為它為用戶提供了更加清晰地使用體驗,清晰在哪?本文主要就是帶你走進它們的世界。
JavaScriptSerializer與Json.net
序列化
我們先定義一個測試用的簡單類--Person:
- public class Person
- {
- public string Name;
- public int Age;
- public Guid TokenId { get; set; }
- public DateTime RegTime;
- public Person Child;
- public Person Friend;
- }
類中的成員僅用來區分不同的變量類型。我們分別以JavaScriptSerializer和Json.net來序列化:
- var person = new 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的情況:
- public class PersonJsConverter : JavaScriptConverter
- {
- public override object 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;
- }
- public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
- {
- Dictionary<string, object> dic = new Dictionary<string, object>();
- var node = obj as Person;
- if (node == null)
- return null;
- if (!string.IsNullOrEmpty(node.Name))
- dic.Add("Name", node.Name);
- //其他字段略...
- return dic;
- }
- public override IEnumerable<Type> SupportedTypes
- {
- get
- {
- return new Type[] { typeof(Person) };
- }
- }
- }
然后在反序列化之前,我們把這個轉換注冊到實體對象中,這時再執行,程序就一切正常了:
- JavaScriptSerializer serializer = new JavaScriptSerializer();
- serializer.RegisterConverters(new JavaScriptConverter[] { new PersonJsConverter(), });
- var deserialize = serializer.Deserialize<Person>(noChildStr);
2、在使用Json.net時,采用了一種更加優雅的方式來處理這個問題--JsonConverter,它可以單獨處理一個指定的類成員變量。這樣就不用像上面的JavaScriptConverter一樣處理整個類的所有成員。代碼如下:
- public class GuidConverter : JsonConverter
- {
- public override bool CanConvert(Type objectType)
- {
- return objectType.IsAssignableFrom(typeof(Guid));
- }
- public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
- {
- try
- {
- return serializer.Deserialize<Guid>(reader);
- }
- catch
- {
- //如果傳進來的值造成異常,則賦值一個初值
- return Guid.Empty;
- }
- }
- public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
- {
- serializer.Serialize(writer, value);
- }
- }
值得注意的是JsonConverter是一個Attribute,所以要在類成員上面添加一個特性:
- public class Person
- {
- public string Name;
- public int Age;
- [JsonConverter(typeof(GuidConverter))]
- public Guid TokenId { get; set; }
- public DateTime RegTime;
- public Person Child;
- public Person Friend;
- }
這時,再運行程序時,TokenId就會被賦上一個初始的值。看來在反序列化中,Json.net還是更勝一籌。
性能
這是網上給出來兩個的性能比較:

綜上,在不考慮系統對第三方控件引用的要求情況下,盡量使用Json.net來進行對象序列化處理。
其他常用的序列化方法
說了半天,回顧一下序列化的定義:
序列化:將對象轉換成字節流的過程,這樣就可以輕松將對象保存在磁盤文件或數據庫中。
反序列化:序列化的逆過程,就是將字節流轉換回原來的對象的過程。
其他各種格式序列化的方法請參考:




