C#笔记——序列化

本文最后更新于:2023年12月7日 晚上

.NET中的序列化

序列化是将对象状态转换为可保持或可传输的形式的过程(比如对象转换为 JSON 格式的输出)。反序列化则是将流转换为对象(比如将 JSON 格式的输入转换为一个对象)。

JSON序列化

在 .NET 中我们可以使用System.Text.Json命名空间向/从 JavaScript 对象表示法(JSON)进行序列化和反序列化。

将 .NET 对象编写为 JSON(序列化)

使用JsonSerializer.Serialize方法直接序列化为字符串

JsonSerializer.Serialize方法可以直接将对象序列化为 JSON 字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
namespace Learning1
{
public enum Gender
{
male,
female
}

public class PersonalInformation
{
public string? Name { get; set; }
public int Age { get; set; }
public Gender Sex { get; set; }
}

public class SerializeLearning
{
public static void Main()
{
var personalInformation = new PersonalInformation
{
Name = "MaP1e",
Age = 18,
Sex = Gender.male,
};

string jsonString = JsonSerializer.Serialize(personalInformation);

Console.WriteLine(jsonString);
}
}
}

创建额外的 JSON 文件

如果需要创建 JSON 文件,加上几句就好:

1
2
string fileName = "PersonalInformation.json";
File.WriteAllText(fileName, jsonString);

当然,我们也可以使用异步代码来创建 JSON 文件:

1
2
3
4
string fileName = "PersonalInformation.json";
using FileStream createStream = File.Create(fileName);
await JsonSerializer.SerializeAsync(createStream, personalInformation);
await createStream.DisposeAsync();
  • 还记得using语句的作用吗?它能够确保发生异常时程序也能够释放可释放实例。
    既然我们创建了 JSON 文件,那我们就不需要打印jsonString了,可以直接读取文件的内容并将其打印出来,看看创建的 JSON 文件里的内容是否正确:Console.WriteLine(File.ReadAllText(filename));

使序列化后的 JSON 字符串具有可读性(美化序列化输出)

但是我们会发现直接转换的 JSON 字符串输出差强人意,不具有易读性,如果对象中还存在着集合属性或者用户定义类型的类的话,就更难读了。为了使输出的 JSON 具有可读性,可以使用JsonSerializerOptions来为序列化进行自定义(客制):

1
2
var options = new JsonSerializerOptions { WriteIndented = true };
srting jsonString = JsonSerializer.Serialize(personalInformation, options);

这将会产生如下输出:

1
2
3
4
5
{
"Name": "MaP1e",
"Age": 18,
"Sex": 0
}

相信有细心的朋友已经发现不对劲的地方了,我们的Sex属性在序列化后直接输出整数值,也就是枚举类型值的原始类型,但是我们想要让其输出我们定义的值,该怎么办呢?请看到后面的“枚举类型的序列化”一节。

序列化需要注意的一些点

参照文档以获得更详细的信息:序列化行为

将 JSON 读取为 .NET 对象(反序列化)

使用JsonSerializer.Deserialize方法对 JSON 字符串进行反序列化

要注意的是,反序列化会默认忽略类中未表示的任何 JSON 属性;而如果类型上的任何属性是必需(required修饰符或[JsonRequired])的,但不存在于 JSON 字符串中,反序列化将失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
namespace Learning1
{
public class WeatherForecast
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string? Summary { get; set; }
}
public class DeserializeLearning
{
public static void Main()
{
string jsonString =
@"{
""Date"": ""2019-08-01T00:00:00-07:00"",
""TemperatureCelsius"": 25,
""Summary"": ""Hot"",
""DatesAvailable"": [
""2019-08-01T00:00:00-07:00"",
""2019-08-02T00:00:00-07:00""
],
""TemperatureRanges"": {
""Cold"": {
""High"": 20,
""Low"": -10
},
""Hot"": {
""High"": 60,
""Low"": 20
}
},
""SummaryWords"": [
""Cool"",
""Windy"",
""Humid""
]
}
";

WeatherForecast? weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString);

Console.WriteLine($"Date: {weatherForecast?.Date}");
Console.WriteLine($"TemperatureCelsius: {weatherForecast?.TemperatureCelsius}");
Console.WriteLine($"Summary: {weatherForecast?.Summary}");
}
}
}

上面产生的输出如下:

1
2
3
Date: 2019/8/1 0:00:00 -07:00
TemperatureCelsius: 25
Summary: Hot

从文件中进行反序列化

其实就是先从文件中读取 JSON 字符串,再把这个 JSON 字符串用JsonSerializer.Deserialize方法反序列化。
同步版本:

1
2
3
string fileName = "WeatherForecast.json";
string jsonString = File.ReadAllText(fileName);
WeatherForecast weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString)!;

异步版本稍微不同,是通过打开一个流,然后将这个流作为参数传入JsonSerializer.Deserialize方法中来进行反序列化。
异步版本:

1
2
3
string fileName = "WeatherForecast.json";
using FileStream openStream = File.OpenRead(fileName);
WeatherForecast? weatherForecast = await JsonSerializer.DeserializeAsync<WeatherForecast>(openStream);

反序列化需要注意的点

参考官方文档:反序列化行为

其实还可以序列化成 UTF-8 和从 UTF-8 反序列化的,但是我懒而且觉得我没那么常用就没写了。

HttpClient 和 HttpContent 扩展方法

我们可以使用HttpClientHttpContent上的扩展方法来序列化和反序列化来自网络的 JSON 有效负载。分别使用GetFromJsonAsyncPostAsJsonAsync方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Comment
{
public int PostId { get; set; }
public int Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
public string? Body { get; set; }
}

public class SerializeLearning
{
public static async Task Main()
{
using HttpClient client = new() { BaseAddress = new Uri("http://jsonplaceholder.typicode.com") };

// Get the comment information.
Comment? comment = await client.GetFromJsonAsync<Comment>("comments/1");
Console.WriteLine($"PostId: {comment?.PostId}");
Console.WriteLine($"Id: {comment?.Id}");
Console.WriteLine($"Name: {comment?.Name}");
Console.WriteLine($"Email: {comment?.Email}");
Console.WriteLine($"Body: {comment?.Body}");

// Post a new comment.
HttpResponseMessage response = await client.PostAsJsonAsync("comments", comment);
Console.WriteLine($"{(response.IsSuccessStatusCode ? "Success" : "Error")} - {response.StatusCode}");
}
}

// 控制台输出
//
// PostId: 1
// Id: 1
// Name: id labore ex et quam laborum
// Email: Eliseo@gardner.biz
// Body: laudantium enim quasi est quidem magnam voluptate ipsam eos
// tempora quo necessitatibus
// dolor quam autem quasi
// reiciendis et nam sapiente accusantium
// Success - Created

枚举类型的序列化

为了解决前面的枚举值序列化后变成了原始整数值类型的问题,我们可以使用JsonStringEnumConverter类。该类能将枚举值转换为字符串以及从字符串转换为枚举值:

1
2
3
4
5
6
7
var options = new JsonSerializerOptions { 
WriteIndented = true,
Converters =
{
new JsonStringEnumConverter()
}
};

也可以通过JsonConverterAttribute属性来批注枚举,以此指定要使用的转换器(注意 .NET8 以上版本才能这么做,否则会报错)。如:

1
2
3
4
5
6
[JsonConverter(typeof(JsonStringEnumConverter<Gender>))]
public enum Gender
{
male,
female
}

XML和SOAP序列化

简单类的序列化

假设现在有这样一个类:

1
2
3
4
public class OrderForm
{
public DataTime OrderDate;
}

这个类经过 XML 序列化后可能如下所示:

1
2
3
<OrderForm>
<OrderDate>12/12/01</OrderDate>
</OrderForm>

可序列化的项

可使用 XmlSerializer类对以下各项进行序列化:

  • 公共类的公共读/写属性和字段
  • 执行ICollectionIEnumerable的类

    仅序列化集合,不序列化公共属性。

  • XmlElement对象
  • XmlNode对象
  • DataSet对象

如何:序列化对象

  1. 创建对象并设置其公共字段和属性。
  2. 使用对象的类型构造XmlSerializer
  3. 调用Serializer方法生成对象的公共属性和字段的XML流或文件表示形式。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    MySerializableClass myObject = new MySerializableClass();

    // Insert code to set properties and fields of the object.
    XmlSerializer mySerializer = new XmlSerializer(typeof(MySerializableClass));

    // To write to a file, create a StreamWriter object.
    StreamWriter myWriter = new StreamWriter("myFileName.xml");
    mySerializer.Serialize(myWriter, myObject);
    myWriter.Close();

如何:反序列化对象

  1. 使用要反序列化的对象的类型构造XmlSerializer
  2. 调用Deserialize方法以生成该对象的副本。在反序列化时,必须将返回的对象强制转换为原始对象的类型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // Construct an instance of the XmlSerializer with the type
    // of object that is being deserialized.
    var mySerializer = new XmlSerializer(typeof(MySerializableClass));

    // To read the file, create a FileStream.
    using var myFileStream = new FileStream("myFileName.xml", FileMode.Open);

    // Call the Deserialize method and cast to the object type.
    var myObject = (MySerializableClass)mySerializer.Deserialize(myFileStream);

这里有一只爱丽丝

希望本文章能够帮到您~


C#笔记——序列化
https://map1e-g.github.io/2023/09/23/CSharp-learning-8/
作者
MaP1e-G
发布于
2023年9月23日
许可协议