序列化
由于指针和引用类型的存在,在运行中的程序中,数据不一定是整块的。
可能东一块西一块散落在内存的各个地方。
序列,是指连续且有序的一个整体。序列化就是把数据变为连续有序整体的过程。
经过这样处理后的数据就可以方便的进行传输和储存了。
Json序列化
json格式
json是一种文本数据格式。用键值对的形式表示数据的名字和数据的内容。
在c#中,时间,数字,字符串及其他的基本类型内置了直接和字符串进行转化的方式。
而复杂类型会通过反射拆解他的成员,一直拆解直到只有基本类型为止。
class Weapon
{
public (int, int) Attack { get; set; }
public float Speed { get; set; }
public int Level { get; set; }
}
{
"Attack": {
"Item1": 10,
"Item2": 20
},
"Speed": 1.5,
"Level": 3
}
序列化api
Newtonsoft.Json
是c#常用的一个json序列化扩展包。通常他会随着模板项目创建一起引用。
如果没有,引入以下命名空间并右键点击安装Newtonsoft.Json
包,VS会自动找到对应的扩展包并下载和引用。
using Newtonsoft.Json;
通过JsonConvert.SerializeObject
方法可以将任意类型序列化为json字符串。
但是默认情况下只会序列化属性,不会序列化字段。
Weapon weapon = new Weapon() { Attack = (10, 20), Speed = 1.5f, Level = 3 };
string json = JsonConvert.SerializeObject(weapon);
Console.WriteLine(json);
使用JsonConvert.DeserializeObject
方法可以将字符串反序列化为类型实例。
需要使用泛型才能精确判断你的目标类型。
string dejson = @"{""Attack"":{""Item1"":10,""Item2"":20},""Speed"":1.5,""Level"":3}";
Weapon deweapon = JsonConvert.DeserializeObject<Weapon>(dejson);
Console.WriteLine(deweapon.Attack);
反序列化是通过反射进行赋值的。
- 因此如果json内容里存在一个你的类型没有的属性,这个属性会被忽略。
- 如果json的内容里不存在你的类型需要的属性,那么这个属性不会被赋值,只会保持默认值。
- 如果你的属性不具有set访问器,他不会被赋值。
- 如果你的构造器里有同名的参数,那么会传递参数给构造器,并且之后不会再对属性赋值。
特性控制序列化规则
特性可以在反射的时候被识别到。但是不同的库是识别的特性可能是不同的。
public class Person
{
// 指定json属性的名称为"name"
[JsonProperty("name")]
public string Name { get; set; }
// 指定json属性的顺序为1
[JsonProperty(Order = 1)]
public int Age { get; set; }
// 指定json属性为null时忽略不写入json中
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string Gender { get; set; }
}
这个库中的特性有很多的设置,详情请参阅其他文档。
Json树
为了读取json的数据,通常会制作一个实体类,然后反序列化。接着通过类实例获取数据。
{
"Name": "Alice",
"Age": 25,
"Hobbies": [
"Reading",
"Cooking",
"Gaming"
]
}
但如果不想这么做,也可以通过解析为Json类型来读取数据。
Json节点类型
- JToken:所有json节点的基类
- Jobject:json对象,用大括号包围的内容,内含多个键值对。
- JProperty:每一对键值对。包括属性的名字和属性的值。
- JValue:属性的值。包括Jobject,Jarray,或基本数据类型例如bool,int,string
- JProperty:每一对键值对。包括属性的名字和属性的值。
- Jarray:json的数组,用中括号包围的内容。里面有一堆并列的值,他们没有名字。
- Jobject:json对象,用大括号包围的内容,内含多个键值对。
解析json
可以从Jobject,JArray,JToken等类型的静态方法Parse
解析字符串。
或者可以通过FromObject
序列化一个实例。
解析出来的JToken可以ToString为序列化后的字符串,或者可以ToObject
反序列化成实例。
string json = @"{
""Name"": ""Alice"",
""Age"": 25,
""Hobbies"": [
""Reading"",
""Cooking"",
""Gaming""
]
}";
var jobject = JObject.Parse(json);
var person = jobject.ToObject<Person>();
jobject = JObject.FromObject(person);
Console.WriteLine(jobject);
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string[] Hobbies { get; set; }
}
获取和修改节点内容
Jobject可以通过索引器来获取子节点的内容。
如果是一个字符串,那么会查找子节点中同名的JProperty。
如果是一个int,那么会在Jarray中查找该索引的内容。
string json = @"{
""Name"": ""Alice"",
""Age"": 25,
""Hobbies"": [
""Reading"",
""Cooking"",
""Gaming""
]
}";
var jobject = JObject.Parse(json);
var jarr = jobject["Hobbies"];
Console.WriteLine(jarr);
Console.WriteLine(jarr[0]);
可以通过索引器修改节点。但对数组添加内容必须通过添加节点方法。
jobject["Hobbies"][0] = "666";
jobject["Hobbies"][2].AddAfterSelf("14.6");
jobject["Date"] = "2012-4-8";
Console.WriteLine(jobject);
对于基本类型,可以通过强制转化来直接序列化。
如果没有对应的键值对,那么引用类型或可为空值类型的强制转换会得到null。
如果是值类型但没有对应的键值对,或者值无法解析为目标类型,那么会有异常。
jobject["Date"] = "2012-4-8";
DateTime date = (DateTime)jobject["Date"];
int? age = (int?)jobject["Age"];
string name = (string)jobject["Name"];
Console.WriteLine(date);
Console.WriteLine(age);
Console.WriteLine(name);