Json的序列化和反序列化的幾種方式
【什麼是Json】
序列化:將物件轉換成位元組流的過程,這樣就可以輕鬆將物件儲存在磁碟檔案或資料庫中。
反序列化:序列化的逆過程,就是將位元組流轉換回原來的物件的過程。
當前主流的序列化JSON字串主要有兩種方式:JavaScriptSerializer及Json.net(Nuget標識:Newtonsoft.Json)。JavaScriptSerializer是微軟官方提供的一種方法,所以如果你用的是asp.netmvc,在Action中如果你返回的語句寫的是”return Json(xxx);“,其實你用的就是JavaScriptSerializer方式。
Json,它是一個輕量級的資料交換格式,我們可以很簡單的來讀取和寫它,並且它很容易被計算機轉化和生成,它是完全獨立於語言的。
【Json支援下面兩種資料結構】
鍵值對的集合--各種不同的程式語言,都支援這種資料結構;
有序的列表型別值的集合--這其中包含陣列,集合,向量,或者序列,等等。
Json有下面幾種表現形式
1.物件
一個沒有順序的“鍵/值”,一個物件以花括號“{”開始,並以花括號"}"結束,在每一個“鍵”的後面,有一個冒號,並且使用逗號來分隔多個鍵值對。例如:
var user = {"name":"Manas","gender":"Male","birthday":"1987-8-8"}
2.陣列
設定值的順序,一個數組以中括號"["開始,並以中括號"]"結束,並且所有的值使用逗號分隔,例如:
var userlist = [{"user":{"name":"Manas","gender":"Male","birthday":"1987-8-8"}},
{"user":{"name":"Mohapatra","Male":"Female","birthday":"1987-7-7"}}]
3.字串
任意數量的Unicode字元,使用引號做標記,並使用反斜槓來分隔。例如:
var userlist = "{\"ID\":1,\"Name\":\"Manas\",\"Address\":\"India\"}"
【序列化和反序列化有三種方式】
1.使用JavaScriptSerializer類
2.使用DataContractJsonSerializer類
3.使用JSON.NET類庫
【示例一】DataContractJsonSerializer類幫助我們序列化和反序列化Json,他在程式集 System.Runtime.Serialization.dll下的System.Runtime.Serialization.Json名稱空間裡。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization;
namespace JsonSerializerAndDeSerializer
{
//服務契約定義了遠端訪問物件和可供呼叫的方法,資料契約則是服務端和客戶端之間要傳送的自定義資料型別。
//一旦宣告一個型別為DataContract,那麼該型別就可以被序列化在服務端和客戶端之間傳送
[DataContract]
public class Person
{
//只有宣告為DataContract的型別的物件可以被傳送,且只有成員屬性會被傳遞,成員方法不會被傳遞。
//WCF對宣告為DataContract的型別提供更加細節的控制,可以把一個成員排除在序列化範圍以外,也就是說,
//客戶端程式不會獲得被排除在外的成員的任何資訊,包括定義和資料。預設情況下,所有的成員屬性都被排除在外,
//因此需要把每一個要傳送的成員宣告為DataMember
//注意:Person實體中的契約 [DataMember],[DataContract],是使用DataContractJsonSerializer序列化和反序列
//化必須要加的,對於其他兩種方式不必加,也可以的。
[DataMember]
public int ID { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public int Age { get; set; }
[DataMember]
public string Sex { get; set; }
}
}
#region 1.DataContractJsonSerializer方式序列化和反序列化
Person stu = new Person()
{
ID = 1,
Name = "老三",
Sex = "男",
Age = 23
};
//序列化
DataContractJsonSerializer js = new DataContractJsonSerializer(typeof(Student));
MemoryStream msObj = new MemoryStream();
//將序列化之後的Json格式資料寫入流中
js.WriteObject(msObj, stu);
msObj.Position = 0;
//從0這個位置開始讀取流中的資料
StreamReader sr = new StreamReader(msObj, Encoding.UTF8);
string json = sr.ReadToEnd();
sr.Close();
msObj.Close();
Console.WriteLine(json);
//反序列化
string toDes = json;
//string to = "{\"ID\":\"1\",\"Name\":\"曹操\",\"Sex\":\"男\",\"Age\":\"1230\"}";
using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(toDes)))
{
DataContractJsonSerializer deseralizer = new DataContractJsonSerializer(typeof(Student));
Student model = (Student)deseralizer.ReadObject(ms);// //反序列化ReadObject
Console.WriteLine("ID=" + model.ID);
Console.WriteLine("Name=" + model.Name);
Console.WriteLine("Age=" + model.Age);
Console.WriteLine("Sex=" + model.Sex);
}
Console.ReadKey();
#endregion
【示例二】
#region 2.JavaScriptSerializer方式實現序列化和反序列化
Student stu = new Student()
{
ID = 1,
Name = "關羽",
Age = 2000,
Sex = "男"
};
JavaScriptSerializer js = new JavaScriptSerializer();
string jsonData = js.Serialize(stu);//序列化
Console.WriteLine(jsonData);
////反序列化方式一:
string desJson = jsonData;
//Student model = js.Deserialize<Student>(desJson);// //反序列化
//string message = string.Format("ID={0},Name={1},Age={2},Sex={3}", model.ID, model.Name, model.Age, model.Sex);
//Console.WriteLine(message);
//Console.ReadKey();
////反序列化方式2
dynamic modelDy = js.Deserialize<dynamic>(desJson); //反序列化
string messageDy = string.Format("動態的反序列化,ID={0},Name={1},Age={2},Sex={3}",
modelDy["ID"], modelDy["Name"], modelDy["Age"], modelDy["Sex"]);//這裡要使用索引取值,不能使用物件.屬性
Console.WriteLine(messageDy);
Console.ReadKey();
#endregion
【示例三】
/使用Json.NET類庫需要引入的名稱空間
//-----------------------------------------------------------------------------
using Newtonsoft.Json;
//-------------------------------------------------------------------------
#region 3.Json.NET序列化
List<Student> lstStuModel = new List<Student>()
{
new Student(){ID=1,Name="張飛",Age=250,Sex="男"},
new Student(){ID=2,Name="潘金蓮",Age=300,Sex="女"}
};
//Json.NET序列化
string jsonData = JsonConvert.SerializeObject(lstStuModel);
Console.WriteLine(jsonData);
Console.ReadKey();
//Json.NET反序列化
string json = @"{ 'Name':'C#','Age':'3000','ID':'1','Sex':'女'}";
Student descJsonStu = JsonConvert.DeserializeObject<Student>(json);//反序列化
Console.WriteLine(string.Format("反序列化: ID={0},Name={1},Sex={2},Sex={3}", descJsonStu.ID, descJsonStu.Name, descJsonStu.Age, descJsonStu.Sex));
Console.ReadKey();
#endregion
【Json格式化輸出】
效果:
//格式化前的字串:
{id=11,name="xxj",age=111,time=DateTime.Now};
//格式化後的字串:
{
id=11,
name="xxj",
age=111,
time='2010-1-1 1:1:1'
};
自定義擴充套件方法
/// <summary>
/// 生成Json格式
/// key=欄位名,value=欄位值
/// </summary>
/// <typeparam name="T">待序列化的物件</typeparam>
/// <returns></returns>
public static string ToJsonSerialize<T>(this T obj) //where T : class
{
if (obj.IsNullOrDbNull())
return string.Empty;
if (obj is DataTable)
return DataTableToJson(obj as DataTable);
JavaScriptSerializer serialize = new JavaScriptSerializer();
//針對日期序列化時區的轉化
var json = Regex.Replace(serialize.Serialize(obj), @"\\/Date(−?\d+)(−?\d+)\\/", match =>
{
var 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");
});
return json;
}
格式化Json程式碼
/// <summary>
/// 測試json字串格式化
/// </summary>
[TestMethod]
public void TestJsonFormat()
{
//格式化json字串
var data = new
{
id=11,
name="xxj",
age=111,
time=DateTime.Now
};
var json = data.ToJsonSerialize();
//Newtonsoft.Json.dll, v4.5.0.0
JsonSerializer serializer = new JsonSerializer();
TextReader tr = new StringReader(json);
JsonTextReader jtr = new JsonTextReader(tr);
object obj = serializer.Deserialize(jtr);
if (obj != null)
{
StringWriter textWriter = new StringWriter();
JsonTextWriter jsonWriter = new JsonTextWriter(textWriter)
{
Formatting = Formatting.Indented,
Indentation = 4,
IndentChar = ' '
};
serializer.Serialize(jsonWriter, obj);
var jsonF = textWriter.ToString();
}
Assert.IsTrue(true);
}
【JavaScriptSerializer與Json.net】
【序列化】
我們先定義一個測試用的簡單類--Person:
public class Person
{
public string Name;
public int Age;
public Guid TokenId;
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進行序列化;
上面綠色為JavaScriptSerializer的結果,下面黃色背景為Json.net的結果,這裡需要注意幾個地方:
1、 JavaScriptSerializer序列化後的時間格式:"\/Date(1441813200214)\/" 表示的是1970年1月1日(DateTime的最小值)到date實際表示的日期之差的總毫秒數。通常我們需要把它轉成標準的時間格式。可以用下面的方法進行字串處理:
jsstr = Regex.Replace(jsstr, @"\\/Date(\d+)(\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>
從上面兩點可以看出,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 Guid.Parse(reader.Value.ToString());
}
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;
}