DataContractSerializer数据不一致下序列化
一、数据类型的等效性
例如下面定义的两个类成员名称、定义顺序都不一样,但是在DataContract、DataMember的Name属性作用下,两个类的实例对象序列化后的xml是一样的,因此Order和OrderV2对于DataContractSerializer序列化是等效的。
[DataContract] public class Order { [DataMember] public int price; [DataMember(Name = "datev2")] public DateTime date; } [DataContract(Name = "Order")] public class OrderV2 { [DataMember] public DateTime datev2; [DataMember(Name = "price")] public int PriceV2; }
二、数据添加导致的不一致
假设客户端定义的是OrderLess,服务端定义的是OrderMore。客户端会基于OrderLess进行序列化后发送到服务端。服务端会基于OrderMore反序列化来自客户端的OrderLess序列化后的消息,能序列化成功,但是会发现缺少Name的值。
[DataContract(Name="Order")] public class OrderLess { [DataMember] public int Price; [DataMember] public DateTime Date;
} [DataContract(Name = "Order")] public class OrderMore { [DataMember] public DateTime Date; [DataMember] public int Price; [DataMember] public string Name; }
此时如果我们想对这个缺失的值设置一个默认值,可以通过注册序列化回调方法实现。
OnDeserialized : 在对象反序列化后立即调用。
OnDeserializing :反序列化对象之前调用。
OnSerialized : 在序列化后调用此。
OnSerializing :在序列化前调用。
对OrderMore修改后如下:
[DataContract(Name = "Order")] public class OrderMore { [DataMember] public DateTime Date; [DataMember] public int Price; [DataMember] public string Name; [OnDeserializing] void OnSerializing(StreamingContext context) { this.Name="未指定"; } }
Main函数:
static void Main(string[] args) { OrderLess less=new OrderLess(); less.Date=DateTime.Now; less.Price=8; Serialize<OrderLess>(less,@"D:\1.txt"); OrderMore more=null; DeSerialize<OrderMore>(@"D:\1.txt",out more); Console.WriteLine("{0} {1} {2}",more.Date,more.Price,more.Name); } public static void Serialize<T>(T t, string path) { DataContractSerializer ser = new DataContractSerializer(typeof(T)); using (XmlTextWriter writer = new XmlTextWriter(path, Encoding.UTF8)) { writer.Formatting = Formatting.Indented; ser.WriteObject(writer, t); } } public static void DeSerialize<T>(string path, out T t) { DataContractSerializer ser = new DataContractSerializer(typeof(T)); using (XmlReader reader = new XmlTextReader(path)) { t = (T)ser.ReadObject(reader); } }
输出:
三、数据删除导致的不一致
与二中的交换一下,客户端定义的是OrderMore,服务端定义的是OrderLess。客户端基于OrderMore类型的序列化器序列化OrderMore对象,服务端按基于OrderLess的序列化器反序列化来自客户端的OrderMore的消息。服务端也能序列化成功,但是当服务端将这个对象返回给客户端时,客户端那边会发现少了Name值。为了避免这种情况发生,OrderLess可以实现IExtensibleDataObject接口。作用是将反序列化时未知的数据存放到ExtensionData,再次对这个对象序列化时就可以还原这些未知对象。
对OrderLess进行修改:
[DataContract(Name="Order")] public class OrderLess:IExtensibleDataObject { [DataMember] public int Price; [DataMember] public DateTime Date; public ExtensionDataObject ExtensionData { get;set; } }
Main函数模拟客户端和服务端的交互过程,先序列化OrderMore,再反序列化为OrderLess,此时将未知的Name属性放在OrderLess的ExtensionData。再次序列化这个OrderLess对象。发现1.txt和2.txt的序列化内容是一样的。
static void Main(string[] args) { OrderMore more = new OrderMore(); more.Date = DateTime.Now; more.Price = 8; more.Name = "OrderMore"; Serialize<OrderMore>(more, @"D:\1.txt"); OrderLess less = null; DeSerialize<OrderLess>(@"D:\1.txt", out less); Serialize<OrderLess>(less, @"D:\2.txt"); }
四、数据契约代理
在上述描述都是数据成员添加或删除这种小差异。当两个对象差异很大时(如下面的A和B),却想让两个对象序列化时等效,需要数据契约代理来做一些工作。
[DataContract] public class A { [DataMember] public string FullName; [DataMember] public int age; } [DataContract] public class B { [DataMember] public string FirstName; [DataMember] public string LastName; [DataMember] public int age; }
MatchA2B实现IDataContractSurrogate接口,在DataContractSerializer序列化过程中能够将一个A对象序列化为B对象,将B对象反序列化为A对象。需要实现GetDataContractType方法,获取需要序列化的实际类型。GetObjectToSerialize序列化前的转换,GetDeserializedObject序列化后的转换。
public class MatchA2B : IDataContractSurrogate { public Type GetDataContractType(Type type) { return type == typeof(A) ? typeof(B) : type; } public object GetObjectToSerialize(object obj, Type targetType) { A a=obj as A; if (a != null) { B b = new B(); b.age = a.age; b.FirstName = a.FullName.Split(" ".ToCharArray())[0]; b.LastName = a.FullName.Split(" ".ToCharArray())[1]; return b; } return obj; } public object GetDeserializedObject(object obj, Type targetType) { B b = obj as B; if (b != null) { A a = new A(); a.FullName = b.FirstName + " " + b.LastName; a.age = b.age; return a; } return obj; } public object GetCustomDataToExport(Type clrType, Type dataContractType){return null;} public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType){return null;} public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes){ } public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData){return null;} public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit) { return null; } }
Main函数:
class Program { static void Main(string[] args) { A a = new A(); a.FullName = "Leonardo DiCaprio"; a.age = 41; Serialize<A>(a, @"D:\1.txt"); A b = null; DeSerialize<A>(@"D:\1.txt", out b); Console.WriteLine("{0} {1} ", b.FullName, b.age); } public static void Serialize<T>(T t, string path) { DataContractSerializer ser = new DataContractSerializer(typeof(T),null,int.MaxValue,false,false,new MatchA2B()); using (XmlTextWriter writer = new XmlTextWriter(path, Encoding.UTF8)) { writer.Formatting = Formatting.Indented; ser.WriteObject(writer, t); } } public static void DeSerialize<T>(string path, out T t) { DataContractSerializer ser = new DataContractSerializer(typeof(T), null, int.MaxValue, false, false, new MatchA2B()); using (XmlReader reader = new XmlTextReader(path)) { t = (T)ser.ReadObject(reader); } } }
1.txt中的内容为,成功的将A对象序列化为B:
<B xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication3"> <FirstName>Leonardo</FirstName> <LastName>DiCaprio</LastName> <age>41</age> </B>