.NET 记录多框架下的Json序列化属性标记问题


有小伙伴反馈,System.Text.Json使用的序列化问题,经了解是System.Text.Json不兼容System.Runtime.Serialization.DataMemenber属性标记

来个demo,

 1 var testMode = new TestMode();
 2 testMode.ID = "aaa";
 3 var serialize = JsonSerializer.Serialize(testMode);
 4 Debug.WriteLine(serialize);
 5 
 6 [DataContract]
 7 public class TestMode
 8 {
 9     [DataMember(Name = "id")]
10     public string ID { get; set; }
11 }

所以DataMember是不适用system.Text.Json的。

我们梳理下各序列化方案下如何正确的使用属性标记

DataContract及DataMemenber

System.Runtime.Serialization下,有数据契约序列化DataContractSerializer,可以用于XML序列化及WCF数据传输

而它的胞弟Json序列化DataContractJsonSerializer,是早期 的.NET Json序列化方案。因性能较弱,Framework下大家逐渐使用NewtonSoft.Json来替换。但简单的Json序列化,依然可以使用DataContractJsonSerializer

DataContractJsonSerializer使用的属性标记是DataContract、DataMember,DataContract用于标记类,DataMember标记序列化的属性:

1 var stopwatch = new Stopwatch();
2 stopwatch.Start();
3 var serializer = new DataContractJsonSerializer(typeof(TestMode));
4 using (var stream = new MemoryStream())
5 {
6     serializer.WriteObject(stream, testMode);
7     string json = Encoding.UTF8.GetString(stream.ToArray());
8     Console.WriteLine($"{json},{stopwatch.ElapsedMilliseconds}ms");
9 }

Newtonsoft.Json是兼容DataContract、DataMember的,所以使用Newtonsoft.Json.JsonConvert可以正常的进行对DataMember的属性进行序列化。:

1 stopwatch.Restart();
2 var serializeObject = JsonConvert.SerializeObject(testMode);
3 Console.WriteLine($"{serializeObject},{stopwatch.ElapsedMilliseconds}ms");

Newtonsoft.Json内部是通过默认配置JsonConvert.DefaultSettings,来兼容DataContract标记,

1 var settings = new JsonSerializerSettings
2 {
3     ContractResolver = new DefaultContractResolver
4     {
5         IgnoreSerializableAttribute = false // 启用 DataContract 支持
6     }
7 };

Newtonsoft.Json虽然对DataMember兼容,但DataContract标记需要注意下:类加DataContract标记后,则只支持有标记DataMember的属性序列化。

输出结果:

DataMember均序列化正常。另外可以发现,在.NET Framework及.NET版本下,首次序列化性能DataContractJsonSerializer比NewtonSoft.Json好很多

System.Text.Json被称作现代Json序列化,System.Text.Json 不识别 [DataMember],如果必须兼容可通过自定义 JsonConverter 间接支持,但复杂。。不推荐

补充下DataMember使用的其它相关标记:

[IgnoreDataMember] – 忽略字段

[DataMember]EmitDefaultValue、Order、IsRequired – 控制默认值、排序、是否必填

我个人挺喜欢用DataContract的,可以明确标记序列化类,与业务数据类隔离开来。

JsonPropertyName

JsonPropertyName是System.Text.Json.Serialization命名空间下的Attribute类,只适用于JsonSerializer序列化方案

微软官方推荐使用,性能比Newtonsoft.Json更好。性能对比可参考 .NET Json序列化方案选择 – 唐宋元明清2188 – 博客园

也补充相关属性标记:

[JsonIgnore] – 忽略属性

[JsonRequired] – 必填

[JsonInclude] – 强制包含如private的私有字段

JsonProperty

JsonProerty是Newtonsoft.Json第三方组件下的属性标记类,专属Newtonsoft.Json-JsonConvert序列化方案

补充相关属性标记

[JsonIgnore] – 忽略属性

[JsonRequired] – 必填

Newtonsoft.Json与System.Text.Json属性标记很相似,不要混用了。

多框架下的属性标记

如果你的组件添加了.NETFramework以及.NET多个框架版本,因考虑不同框架下的最佳Json序列化性能,那你的序列化属性类可能就需要处理不同的属性标记了

同时给一个属性添加多个标记能解决问题:

1     [JsonProperty("id")]
2     [JsonPropertyName("id")]
3     [DataMember(Name = "id",)]
4     public string ID { get; set; }

但不建议,容易造成后续维护的风险,可能不小心就遗漏了一个。

最佳方案是,通过明确的框架类型来区分不同代码

引用组件:

1 #if NETFRAMEWORK
2 using Newtonsoft.Json;
3 #elif NET8_0
4 using System.Text.Json.Serialization;
5 #endif

添加序列化属性标记:

1 #if NETFRAMEWORK
2     [JsonProperty("id")]
3 #elif NET8_0
4     [JsonPropertyName("id")]
5 #endif

一个框架下,建议明确并统一使用一个序列化方案。性能对比可参考 .NET Json序列化方案选择 – 唐宋元明清2188 – 博客园