在某些情况下,由于业务需求的不同,需要实现实体对象的继承,但是在输出 JSON 的时候,希望只输出基类的属性,而不要输出派生类型的属性,以避免产生不可控制的数据泄露问题;那么,我们可以采用下面的序列化设置。比如下面的 UserInfoExtension 派生自 UserInfo,并扩展了一个属性为身份证的属性,在输出 JSON 的时候,我们希望不要序列化派生类,那么我们可以在 Serialize 序列化的时候,指定序列化的类型为基类:UserInfo,即可达到隐藏派生类属性的目的。
public class UserInfo { [JsonPropertyName("name")] public string Name { get; set; } public decimal Money { get; set; } [JsonIgnore] public int Age { get; set; } public int Password { get; } public string Remark { get; set; } } public class UserInfoExtension : UserInfo { public string IdCard { get; set; } } var user = new UserInfoExtension { Name = "Ron", Money = 4.5m, Age = 30, Remark = null }; var json = JsonSerializer.Serialize(user, typeof(UserInfo)); // 输出 {"name":"Ron","Money":4.5,"Password":0,"Remark":null} 仅输出指定属性(排除属性的逆向操作)在 Newtonsoft.Json 中,我们可以通过指定 MemberSerialization 和 JsonProperty 来实现输出指定属性到 JSON 中,比如下面的代码
[Newtonsoft.Json.JsonObject(Newtonsoft.Json.MemberSerialization.OptIn)] public class UserInfo { [Newtonsoft.Json.JsonProperty("name")] public string Name { get; set; } public int Age { get; set; } } var user = new UserInfo() { Age = 18, Name = "Ron" }; var json = Newtonsoft.Json.JsonConvert.SerializeObject(user); // 输出 {"name":"Ron"}不过,很遗憾的告诉大家,目前 System.Text.Json 不支持这种方式;为此,我特意去看了 corefx 的 issue,我看到了下面这个反馈
现在可以方向了,当 .NETCore 合并到主分支 .NET 也就是 .NET5.0 的时候,官方将提供支持,在此之前,还是使用推荐 Newtonsoft.Json 。
在反序列化的时候,允许 JSON 文本包含注释默认情况下,System.Text.JSON 不支持源JSON 文本包含注释,比如下面的代码,当你不使用 ReadCommentHandling = JsonCommentHandling.Skip 的设置的时候,将抛出异常,因为在字段 Age 的后面有注释 /* age */。
var jsonText = "{\"Name\":\"Ron\",\"Money\":4.5,\"Age\":30/* age */}"; var options = new JsonSerializerOptions { ReadCommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true, }; var user = JsonSerializer.Deserialize<UserInfoExtension>(jsonText); 允许字段溢出在接口数据出现变动时,极有可能出现源 JSON 文本和实体对象属性不匹配的问题,JSON 中可能会多出一些实体对象不存在的属性,这种情况我们称之为“溢出”,在默认情况下,溢出的属性将被忽略,如果希望捕获这些“溢出”的属性,可以在实体对象中声明一个类型为:Dictionary<string, object> 的属性,并对其应用特性标记:JsonExtensionData。
为了演示这种特殊的处理,我们声明了一个实体对象 UserInfo,并构造了一个 JSON 源,该 JSON 源包含了一个 UserInfo 不存在的属性:Money,预期该 Money 属性将被反序列化到属性 ExtensionData 中。
public class UserInfo { public string Name { get; set; } public int Age { get; set; } [JsonExtensionData] public Dictionary<string, object> ExtensionData { get; set; } } var jsonText = "{\"Name\":\"Ron\",\"Money\":4.5,\"Age\":30}"; var user = JsonSerializer.Deserialize<UserInfo>(jsonText);输出截图
有意思的是,被特性 JsonExtensionData 标记的属性,在序列化为 JSON 的时候,他又会将 ExtensionData 的字典都序列化为单个 JSON 的属性,这里不再演示,留给大家去体验。
转换器System.Text.Json 内置了各种丰富的类型转换器,这些默认的转换器在程序初始化 JsonSerializerOptions 的时候就默认加载,在 JsonSerializerOptions 内部,维护着一个私有静态成员 s_defaultSimpleConverters,同时还有一个公有属性 Converters ,Converters 属性在 JsonSerializerOptions 的构造函数中被初始化;从下面的代码中可以看到,默认转换器集合和公有转换器集是相互独立的,System.Text.Json 允许开发人员通过 Converters 添加自定义的转换器。
public sealed partial class JsonSerializerOptions { // The global list of built-in simple converters. private static readonly Dictionary<Type, JsonConverter> s_defaultSimpleConverters = GetDefaultSimpleConverters(); // The global list of built-in converters that override CanConvert(). private static readonly List<JsonConverter> s_defaultFactoryConverters = GetDefaultConverters(); // The cached converters (custom or built-in). private readonly ConcurrentDictionary<Type, JsonConverter> _converters = new ConcurrentDictionary<Type, JsonConverter>(); private static Dictionary<Type, JsonConverter> GetDefaultSimpleConverters() { ... } private static List<JsonConverter> GetDefaultConverters() { ... } public IList<JsonConverter> Converters { get; } ... } 内置转换器