C# 对象序列化之序列化为Json文件(一)
目录1.概念1.1原理1.2用途1.3 JSON序列化1.4 二进制和XML序列化2. 序列化为JSON2.1 简单的序列化2.2 复杂的序列化3 忽略属性3.1 忽略单个属性3.2 忽略所有只读属性3.3 忽略所有Null的属性4 序列化为格式化的JSON5 自定义属性名称和值5.1自定义单个属性名称5.2对所有 JSON 属性名称使用 camel 大小写5.3使用自定义 JSON 属性命名策略
目录
1.概念
序列化是指将对象转换成字节流,从而存储对象或将对象传输到内存、数据库或文件的过程。 它的主要用途是保存对象的状态,以便能够在需要时重新创建对象。 反向过程称为“反序列化”。
1.1原理
将对象序列化为带有数据的流。 该流还可能包含有关对象类型的信息,例如其版本、区域性和程序集名称。 可以将此流中的对象存储在数据库、文件或内存中。
1.2用途
通过序列化,开发人员可以保存对象的状态,并能在需要时重新创建对象,同时还能存储对象和交换数据。 通过序列化,开发人员可以执行如下操作:
- 使用 Web 服务将对象发送到远程应用程序
- 将对象从一个域传递到另一个域
- 将对象通过防火墙传递为 JSON 或 XML 字符串
- 跨应用程序维护安全或用户特定的信息
1.3 JSON序列化
System.Text.Json 命名空间包含用于 JavaScript 对象表示法 (JSON) 序列化和反序列化的类。 JSON 是一种常用于在 Web 上共享数据的开放标准。
JSON 序列化将对象的公共属性序列化为符合 RFC 8259 JSON 规范的字符串、字节数组或流。 若要控制 JsonSerializer 对类的实例进行序列化或反序列化的方法,请执行以下操作:
- 使用 JsonSerializerOptions 对象
- 将 System.Text.Json.Serialization 命名空间中的特性应用于类或属性
- 实现自定义转换器
1.4 二进制和XML序列化
System.Runtime.Serialization 命名空间包含用于对二进制和 XML 进行序列化和反序列化的类。
二进制序列化使用二进制编码来生成精简的序列化以供使用,如基于存储或套接字的网络流。 在二进制序列化中,所有成员(包括只读成员)都会被序列化,且性能也会有所提升。
警告:二进制序列化可能会十分危险。 有关详细信息,请参阅BinaryFormatter security guide。
XML 序列化将对象的公共字段和属性或方法的参数和返回值序列化成符合特定 XML 架构定义语言 (XSD) 文档要求的 XML 流。 XML 序列化生成已转换成 XML 的强类型类,其中包含公共属性和字段。 System.Xml.Serialization 包含用于对 XML 进行序列化和反序列化的类。 将特性应用于类和类成员,从而控制 XmlSerializer 如何序列化或反序列化类的实例。
2. 序列化为JSON
2.1 简单的序列化
假设有这样一个对象:
public class WeatherForecast
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}
你要将其实例序列化,并写入到Json文件:
static void Main(string[] args)
{
WeatherForecast weatherForecast = new WeatherForecast
{
Date = DateTime.Now,
TemperatureCelsius = 20,
Summary = "it is a good day!"
};
string jsonString = JsonSerializer.Serialize(weatherForecast);
// 异步序列化
//using FileStream createStream = File.Create(fileName);
//await JsonSerializer.SerializeAsync(createStream, weatherForecast);
File.WriteAllText("weather.json", jsonString);
}
2.2 复杂的序列化
假设有这样一个更复杂的类:
public class WeatherForecastWithPOCOs
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
public string SummaryField;
public IList<DateTimeOffset> DatesAvailable { get; set; }
public Dictionary<string, HighLowTemps> TemperatureRanges { get; set; }
public string[] SummaryWords { get; set; }
}
public class HighLowTemps
{
public int High { get; set; }
public int Low { get; set; }
}
这次我们实行反序列化实验,假设再你的程序目录下已经有一个Json文件(我取名为:weatherPocos.json):
{
"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"
]
}
将Json序列化为WeatherForecastWithPOCOs对象:
string jsonString2 = File.ReadAllText("weatherPocos.json");
var weatherPoco = JsonSerializer.Deserialize<WeatherForecastWithPOCOs>(jsonString2);
//异步操作
//using FileStream openStream = File.OpenRead(fileName);
//weatherForecast = await JsonSerializer.DeserializeAsync<WeatherForecast>(openStream);
Console.WriteLine(weatherPoco.Date);
3 忽略属性
将 C# 对象序列化为 JavaScript 对象表示法 (JSON) 时,默认情况下,所有公共属性都会序列化。 如果不想让某些属性出现在生成的 JSON 中,则有几个选项可用。 本文将介绍如何根据各种条件忽略属性:
3.1 忽略单个属性
若要忽略单个属性,请使用 [JsonIgnore] 特性。
下面是要进行序列化的示例类型和 JSON 输出:
public class WeatherForecastWithIgnoreAttribute
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
[JsonIgnore]
public string Summary { get; set; }
}
这样,当我们序列化上面的对象时,json文件如下:
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
}
3.2 忽略所有只读属性
如果属性包含公共 getter 而不是公共 setter,则属性为只读。 若要在序列化时忽略所有只读属性,请将 JsonSerializerOptions.IgnoreReadOnlyProperties 设置为 true
,如以下示例中所示:
var options = new JsonSerializerOptions
{
IgnoreReadOnlyProperties = true,
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);
举个例子:
定义类如下:
public class WeatherForecastWithROProperty
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
public int WindSpeedReadOnly { get; private set; } = 35;
}
序列化的结果:
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
}
此选项仅适用于序列化。 在反序列化过程中,默认情况下会忽略只读属性。
3.3 忽略所有Null的属性
若要在序列化时忽略所有 null 值属性,请将 IgnoreNullValues 属性设置为 true
,如以下示例中所示:
var options = new JsonSerializerOptions
{
IgnoreNullValues = true,
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);
倘若对象如下:
Property | 值 |
---|---|
Date | 8/1/2019 12:00:00 AM -07:00 |
TemperatureCelsius | 25 |
Summary | null |
实际结果为:
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25
}
4 序列化为格式化的JSON
默认情况下,序列化得到的JSON文件会被压缩,也就是我们上面看到的树形结构会压缩成一行,如下:
{"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"]}
若要输出带格式的JSON,需要将 JsonSerializerOptions.WriteIndented 设置为 true
如果你通过相同的选项重复使用 JsonSerializerOptions
,则请勿在每次使用时都创建新的 JsonSerializerOptions
实例。对每个调用重复使用同一实例。 有关详细信息,请参阅重用 JsonSerializerOptions 实例。
5 自定义属性名称和值
默认情况下,序列化为Json的每个key名和你类中的会保持完全一致,但是你也可以进行修改,以达到自定义。
5.1 自定义单个属性名称
若要设置单个属性的名称,请使用 [JsonPropertyName] 特性。下面是要进行序列化的示例类型和生成的 JSON:
public class WeatherForecastWithPropertyNameAttribute
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
[JsonPropertyName("Wind")]
public int WindSpeed { get; set; }
}
你会看到WindSpeed的key 变为Wind:
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"Wind": 35
}
此特性设置的属性名称:
- 同时适用于两个方向(序列化和反序列化)。
- 优先于属性命名策略。
5.2 对所有 JSON 属性名称使用 camel 大小写
若要对所有 JSON 属性名称使用 camel 大小写,请将 JsonSerializerOptions.PropertyNamingPolicy 设置为 JsonNamingPolicy.CamelCase
,如下面的示例中所示:
var serializeOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);
public class WeatherForecastWithPropertyNameAttribute
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
[JsonPropertyName("Wind")]
public int WindSpeed { get; set; }
}
{
"date": "2019-08-01T00:00:00-07:00",
"temperatureCelsius": 25,
"summary": "Hot",
"Wind": 35
}
camel 大小写属性命名策略:
- 适用于序列化和反序列化。
- 由
[JsonPropertyName]
特性替代。 这便是示例中的 JSON 属性名称Wind
不是 camel 大小写的原因。
5.3 使用自定义 JSON 属性命名策略
若要使用自定义 JSON 属性命名策略,请创建派生自 JsonNamingPolicy 的类,并替代 ConvertName 方法,如下面的示例中所示:
using System.Text.Json;
namespace SystemTextJsonSamples
{
public class UpperCaseNamingPolicy : JsonNamingPolicy
{
public override string ConvertName(string name) =>
name.ToUpper();
}
}
然后,将 JsonSerializerOptions.PropertyNamingPolicy 属性设置为命名策略类的实例:
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = new UpperCaseNamingPolicy(),
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);
下面是要进行序列化的示例类和 JSON 输出:
public class WeatherForecastWithPropertyNameAttribute
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
[JsonPropertyName("Wind")]
public int WindSpeed { get; set; }
}
{
"DATE": "2019-08-01T00:00:00-07:00",
"TEMPERATURECELSIUS": 25,
"SUMMARY": "Hot",
"Wind": 35
}
JSON 属性命名策略:
- 适用于序列化和反序列化。
- 由
[JsonPropertyName]
特性替代。 这便是示例中的 JSON 属性名称Wind
不是大写的原因。
5.4 序列化字典
如果要序列化的对象的属性为 Dictionary<string,TValue>
类型,则 string
键可转换为 camel 大小写。 为此,请将 DictionaryKeyPolicy 设置为 JsonNamingPolicy.CamelCase
,如下面的示例中所示:
var options = new JsonSerializerOptions
{
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);
使用名为 TemperatureRanges
且具有键值对 "ColdMinTemp", 20
和 "HotMinTemp", 40
的字典序列化对象会产生类似于以下示例的 JSON 输出:
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"TemperatureRanges": {
"coldMinTemp": 20,
"hotMinTemp": 40
}
}
字典键的 camel 大小写命名策略仅适用于序列化。 如果对字典进行反序列化,即使为 DictionaryKeyPolicy
指定 JsonNamingPolicy.CamelCase
,键也会与 JSON 文件匹配
5.5 作为字符串的枚举
默认情况下,枚举会序列化为数字。 若要将枚举名称序列化为字符串,请使用 JsonStringEnumConverter。
例如,假设需要序列化以下具有枚举的类:
public class WeatherForecastWithEnum
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public Summary Summary { get; set; }
}
public enum Summary
{
Cold, Cool, Warm, Hot
}
如果 Summary 为 Hot
,则默认情况下序列化的 JSON 具有数值 3:
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": 3
}
下面的示例代码序列化枚举名称(而不是数值),并将名称转换为 camel 大小写:
options = new JsonSerializerOptions
{
WriteIndented = true,
Converters =
{
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)
}
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);
生成的 JSON 类似于以下示例:
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "hot"
}
options = new JsonSerializerOptions
{
Converters =
{
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)
}
};
weatherForecast = JsonSerializer.Deserialize<WeatherForecastWithEnum>(jsonString, options);
5.6 不区分大小写的属性匹配
默认情况下,反序列化会查找 JSON 与目标对象属性之间区分大小写的属性名称匹配。 若要更改该行为,请将 JsonSerializerOptions.PropertyNameCaseInsensitive 设置为 true
:
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
var weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString, options);
下面是具有 camel 大小写属性名称的示例 JSON。 它可以反序列化为具有帕斯卡拼写法属性名称的以下类型。
{
"date": "2019-08-01T00:00:00-07:00",
"temperatureCelsius": 25,
"summary": "Hot",
}
public class WeatherForecast
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}
6 处理溢出 JSON
反序列化时,可能会在 JSON 中收到不是由目标类型的属性表示的数据。 例如,假设目标类型如下:
public class WeatherForecast
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}
要反序列化的 JSON 如下:
{
"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"
],
"SummaryWords": [
"Cool",
"Windy",
"Humid"
]
}
如果将显示的 JSON 反序列化为显示的类型,则 DatesAvailable
和 SummaryWords
属性无处可去,会丢失。 若要捕获额外数据(如这些属性),请将 [JsonExtensionData] 特性应用于类型 Dictionary<string,object>
或 Dictionary<string,JsonElement>
的属性:
public class WeatherForecastWithExtensionData
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
[JsonExtensionData]
public Dictionary<string, object> ExtensionData { get; set; }
}
将前面显示的 JSON 反序列化为此示例类型时,额外数据会成为 ExtensionData
属性的键值。
Property | 值 | 说明 |
---|---|---|
Date | "8/1/2019 12:00:00 AM -07:00" | |
TemperatureCelsius | 0 | 区分大小写的不匹配(JSON 中的 temperatureCelsius ),因此未设置属性。 |
Summary | "Hot" | |
ExtensionData | temperatureCelsius: 25 | 因为大小写不匹配,所以此 JSON 属性是额外属性,会成为字典中的键值对。 |
DatesAvailable | [ "8/1/2019 12:00:00 AM -07:00", "8/2/2019 12:00:00 AM -07:00" ] | JSON 中的额外属性会成为键值对,将数组作为值对象。 |
SummaryWords | [ "Cool", "Windy", "Humid" ] | JSON 中的额外属性会成为键值对,将数组作为值对象。 |
序列化目标对象时,扩展数据键值对会成为 JSON 属性,就如同它们处于传入 JSON 中一样:
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 0,
"Summary": "Hot",
"temperatureCelsius": 25,
"DatesAvailable": [
"2019-08-01T00:00:00-07:00",
"2019-08-02T00:00:00-07:00"
],
"SummaryWords": [
"Cool",
"Windy",
"Humid"
]
}
请注意,ExtensionData
属性名称不会出现在 JSON 中。 此行为使 JSON 可以进行往返,而不会丢失任何不会以其他方式进行反序列化的额外数据。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)