解决一个C# 在Framework 4.5反序列化的问题

概要

本文解决一个运行在Framework 4.5的旧系统,在作Change时候遇到的兼容性问题。原有的JSON结果发生了变化,但是在解析和反序列化的时候,需要同时兼容新旧JSON格式。

问题提出

随着JSON的格式发生变化,类的定义也要发生变化,具体如下:

原有的JSON格式如下:

javascript 复制代码
{
   "Name": "John",
   "Age"": 20,
   "Scores"": [90, 85, 88],
   "Details"": {"grade": 10, "school": "High School"},
   "Address": {"City": "New York", "Region":"ABC"}
}

新的JSON格式:

javascript 复制代码
{
   "Name": "John",
   "Age"": 20,
   "Scores"": [90, 85, 88],
   "Details"": {"grade": 10, "school": "High School"},
   "Address": {"City": "New York", "Region": {
       ""Code"":"111",
       ""Name"": "GGG"
   }
}

原有的Address类定义如下:

csharp 复制代码
 [DataContract]
 public class Address
 {
     [DataMember]
     public string City { get; set; }
 }
[DataContract]
public class AddressV0 : Address
{
    [DataMember]
    public Region Region { get; set; }
}

随着Address的结果发生变化,但是新旧格式都要支持,所以增加AddressV1类,重新定义Region。

csharp 复制代码
[DataContract]
public class AddressV1 : Address
{
    [DataMember]
    public Region Region { get; set; }
}
[DataContract]
public class Region
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public string Code { get; set; }
}

因为要同时支持AddressV1的结构和AddressV0的结构,所以在Student类中新增AddressV1字段

csharp 复制代码
[DataContract]
public class Student
{
    [DataMember]
    public AddressV0 Address { get; set; }

    [DataMember]
    public AddressV1 AddressV1 { get; set; }
}

但是新的JSON格式中地址字段的名称是Address,并不是AddressV1,所以无法直接反序列成需要的对象。

另一个限制是现有的反序列方式是通过System.Runtime.Serialization的库完成,虽然功能有限,但是考虑到系统的稳定运行要求,不能因为这次的change,更换成Newtonsoft等当前流行的代码库。

解决方法

使用OnDeserialized属性,该属性是一个方法,这个方法在反序列完成后触发。因此,先把Address和AddressV1 对象从自动反序列话改成手动反序列化,代码如下:

csharp 复制代码
[DataContract]
public class Student
{
    [IgnoreDataMember]
    public AddressV0 Address { get; set; }

    [IgnoreDataMember]
    public AddressV1 AddressV1 { get; set; }
}

我们使用OnDeserialized定制反序列话过程,需要原始的JSON字符串。但是很遗憾,该方法虽然有一个StreamingContext参数,但是现有系统使用的是Framework 4.5,StreamingContext并不包含原始的JSON字符串。

解决方法是在Student定义一个静态字段originalString,每次反序列之前,手动传入原始的JSON字符串,并且该字段不出现在对Student对象序列化时候的字符串中。代码如下:

csharp 复制代码
[DataContract]
public class Student
{
    [IgnoreDataMember]
    public AddressV0 Address { get; set; }

    [IgnoreDataMember]
    public AddressV1 AddressV1 { get; set; }
	
	[IgnoreDataMember]
	public static string originalString = string.Empty;
}

这样我们在Student中,重新定义OnDeserialized方法,在该方法中,根据原始JSON字符串判断是要反序列化AddressV0 对象还是AddressV1 对象。

csharp 复制代码
[DataContract]
public class Student
{
    [IgnoreDataMember]
    public AddressV0 Address { get; set; }

    [IgnoreDataMember]
    public AddressV1 AddressV1 { get; set; }
	
	[IgnoreDataMember]
	public static string originalString = string.Empty;
	
	 [OnDeserialized]
	 private void OnDeserialized(StreamingContext context)
	 {
	     if (Student.originalString == string.Empty)
	     {
	         return;
	     }
	     JObject obj = JObject.Parse(Student.originalString);
	     var addressToken = obj.SelectToken("Address");
	     var regionToken = addressToken?.SelectToken("Region");
	     var serializer = new DataContractJsonSerializer(typeof(AddressV0));
	     using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(addressToken!.ToString())))
	     {
	         if (regionToken?.Type == JTokenType.Object)
	         {
	             serializer = new DataContractJsonSerializer(typeof(AddressV1));
	             this.AddressV1 = serializer.ReadObject(ms) as AddressV1 ;
	             return;
	         }
	         this.Address = serializer.ReadObject(ms) as AddressV0;
	         return;
	     }   
	 }
}

具体的判断方法,采用Newtonsoft库的JObject 和JToken,因为它们可以精确的判断Region字段是字符串还是对象,最后生成对象,还是使用DataContractJsonSerializer进行反序列化,和系统原有的方式一致,避免造成兼容问题。

结论

在旧系统做change时候,需要按照修旧如旧的原则。如果change涉及主流程的修改,更应该小心,我们可以时候第三方库作为辅助,但是不能直接应用,避免产生各种兼容性问题。

相关推荐
崔庆才丨静觅1 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
曹牧2 小时前
Spring Boot:如何测试Java Controller中的POST请求?
java·开发语言
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅2 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
爬山算法3 小时前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
懒人咖3 小时前
缺料分析时携带用料清单的二开字段
c#·金蝶云星空
kfyty7253 小时前
集成 spring-ai 2.x 实践中遇到的一些问题及解决方案
java·人工智能·spring-ai
猫头虎3 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven