解决一个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涉及主流程的修改,更应该小心,我们可以时候第三方库作为辅助,但是不能直接应用,避免产生各种兼容性问题。

相关推荐
程序员黑豆19 小时前
全新系列开启:AI 全栈开发
前端·后端·全栈
要开心吖ZSH19 小时前
AI医疗分诊与健康咨询助手agent开发——(0)项目背景与概要
java·ai·agent·健康医疗·rag
小小小小宇19 小时前
Partial Clone
前端
小小小小宇19 小时前
git sparse-checkout(稀疏检出)
前端
ZC跨境爬虫20 小时前
跟着 MDN 学JavaScript day_9:字符串方法实战挑战与解题思路
开发语言·前端·javascript
后青春期的诗go20 小时前
泛微OA-E9与第三方系统集成开发企业级实战记录(十五)
java·泛微·集成开发·e9
夜焱辰20 小时前
WebMCP 的正确打开方式:只注册 2 个工具,代理 N 个——CreatorWeave 的 On-Demand 实践
前端
吃口巧乐兹20 小时前
理解 Agent 中的 Slash Command:从概念到自定义命令实践
java·github
用户74595717484020 小时前
Fabric:Python SSH 远程执行利器
前端
用户2883919274721 小时前
Elasticsearch DSL:用 Python 对象写查询,不用再手写 JSON
前端