c# – 引用自动创建的对象
我试图序列化和反序列化复杂的对象图:
A类包含一个只读属性,包含B类型的不可变对象数组.B类型的对象以及不可变数组是在类型A的构造函数中创建的. 其他类型包含对通过访问类型A的对象的数组获得的类型B的对象的引用. 在反序列化期间,我需要对B的任何引用最终指向由A构造函数通过索引创建的适当对象,而不是从JSON创建全新的B对象.我正在尝试将PreserveReferencesHandling与JSON.NET一起使用.这是可行的,因为它试图使用B的反序列化版本而不是A构造的版本. 我可以在这里使用另一种策略而不修改我的类型吗? 编辑:为了澄清并明确说明,解决方案不得修改类型本身.您可以触摸合约解析器,活页夹,参考解析器等,但不能触摸类型.此外,B类型无法反序列化.它们必须由A的构造函数构成. 解决方法
更新
你的问题没有举例说明你想要完成什么,所以我猜你的一些设计要求.要确认,您的情况是: >您有一些复杂的对象图,可以使用Json.NET进行序列化 让我们用以下类来模拟这种情况: public abstract class B { public int Index { get; set; } // Some property that could be modified. } public class A { public class BActual : B { } static int nextId = -1; readonly B[] items; // A private read-only array that is never changed. public A() { items = Enumerable.Range(101 + 10 * Interlocked.Increment(ref nextId),2).Select(i => new BActual { Index = i }).ToArray(); } public string SomeProperty { get; set; } public IEnumerable<B> Items { get { foreach (var b in items) yield return b; } } public string SomeOtherProperty { get; set; } } public class MidClass { public MidClass() { AnotherA = new A(); } public A AnotherA { get; set; } } public class MainClass { public MainClass() { A1 = new A(); MidClass = new MidClass(); A2 = new A(); } public List<B> ListOfB { get; set; } public A A2 { get; set; } public MidClass MidClass { get; set; } public A A1 { get; set; } } 然后,要序列化,您需要使用Json.NET来收集对象图中的所有A实例.接下来,在设置了 要使用PreserveReferencesHandling.Objects进行反序列化,必须使用JsonConverter反序列化代理类,以反序列化A和B的属性(如果有),并将序列化的“$ref”引用的adds a reference反序列化为B分配给的新B实例. A.的构造函数 从而: // Used to enable Json.NET to traverse an object hierarchy without actually writing any data. public class NullJsonWriter : JsonWriter { public NullJsonWriter() : base() { } public override void Flush() { // Do nothing. } } public class TypeInstanceCollector<T> : JsonConverter where T : class { readonly List<T> instanceList = new List<T>(); readonly HashSet<T> instances = new HashSet<T>(); public List<T> InstanceList { get { return instanceList; } } public override bool CanConvert(Type objectType) { return typeof(T).IsAssignableFrom(objectType); } public override bool CanRead { get { return false; } } public override object ReadJson(JsonReader reader,Type objectType,object existingValue,JsonSerializer serializer) { throw new NotImplementedException(); } public override void WriteJson(JsonWriter writer,object value,JsonSerializer serializer) { T instance = (T)value; if (!instances.Contains(instance)) { instanceList.Add(instance); instances.Add(instance); } // It's necessary to write SOMETHING here. Null suffices. writer.WriteNull(); } } public class ADeserializer : JsonConverter { public override bool CanConvert(Type objectType) { return typeof(A).IsAssignableFrom(objectType); } public override bool CanWrite { get { return false; } } public override object ReadJson(JsonReader reader,JsonSerializer serializer) { var obj = JObject.Load(reader); if (obj == null) return existingValue; A a; var refId = (string)obj["$ref"]; if (refId != null) { a = (A)serializer.ReferenceResolver.ResolveReference(serializer,refId); if (a != null) return a; } a = ((A)existingValue) ?? new A(); var items = obj["Items"]; obj.Remove("Items"); // Populate properties other than the items,if any // This also updates the ReferenceResolver table. using (var objReader = obj.CreateReader()) serializer.Populate(objReader,a); // Populate properties of the B items,if any if (items != null) { if (items.Type != JTokenType.Array) throw new JsonSerializationException("Items were not an array"); var itemsArray = (JArray)items; if (a.Items.Count() < itemsArray.Count) throw new JsonSerializationException("too few items constructucted"); // Item counts must match foreach (var pair in a.Items.Zip(itemsArray,(b,o) => new { ItemB = b,JObj = o })) { #if false // If your B class has NO properties to deserialize,do this var id = (string)pair.JObj["$id"]; if (id != null) serializer.ReferenceResolver.AddReference(serializer,id,pair.ItemB); #else // If your B class HAS SOME properties to deserialize,do this using (var objReader = pair.JObj.CreateReader()) { // Again,Populate also updates the ReferenceResolver table serializer.Populate(objReader,pair.ItemB); } #endif } } return a; } public override void WriteJson(JsonWriter writer,JsonSerializer serializer) { throw new NotImplementedException(); } } public class RootProxy<TRoot,TTableItem> { [JsonProperty("table",Order = 1)] public List<TTableItem> Table { get; set; } [JsonProperty("data",Order = 2)] public TRoot Data { get; set; } } public class TestClass { public static string Serialize(MainClass main) { // First,collect all instances of A var collector = new TypeInstanceCollector<A>(); var collectionSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects,Converters = new JsonConverter[] { collector } }; using (var jsonWriter = new NullJsonWriter()) { JsonSerializer.CreateDefault(collectionSettings).Serialize(jsonWriter,main); } // Now serialize a proxt class with the collected instances of A at the beginning,to establish reference ids for all instances of B. var proxy = new RootProxy<MainClass,A> { Data = main,Table = collector.InstanceList }; var serializationSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects }; return JsonConvert.SerializeObject(proxy,Formatting.Indented,serializationSettings); } public static MainClass Deserialize(string json) { var serializationSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects,Converters = new JsonConverter[] { new ADeserializer() } }; var proxy = JsonConvert.DeserializeObject<RootProxy<MainClass,A>>(json,serializationSettings); return proxy.Data; } static IEnumerable<A> GetAllA(MainClass main) { // For testing. In your case apparently you can't do this manually. if (main.A1 != null) yield return main.A1; if (main.A2 != null) yield return main.A2; if (main.MidClass != null && main.MidClass.AnotherA != null) yield return main.MidClass.AnotherA; } static IEnumerable<B> GetAllB(MainClass main) { return GetAllA(main).SelectMany(a => a.Items); } public static void Test() { var main = new MainClass(); main.A1.SomeProperty = "main.A1.SomeProperty"; main.A1.SomeOtherProperty = "main.A1.SomeOtherProperty"; main.A2.SomeProperty = "main.A2.SomeProperty"; main.A2.SomeOtherProperty = "main.A2.SomeOtherProperty"; main.MidClass.AnotherA.SomeProperty = "main.MidClass.AnotherA.SomeProperty"; main.MidClass.AnotherA.SomeOtherProperty = "main.MidClass.AnotherA.SomeOtherProperty"; main.ListOfB = GetAllB(main).Reverse().ToList(); var json = Serialize(main); var main2 = Deserialize(json); var json2 = Serialize(main2); foreach (var b in main2.ListOfB) Debug.Assert(GetAllB(main2).Contains(b)); // No assert Debug.Assert(json == json2); // No assert Debug.Assert(main.ListOfB.Select(b => b.Index).SequenceEqual(main2.ListOfB.Select(b => b.Index))); // No assert Debug.Assert(GetAllA(main).Select(a => a.SomeProperty + a.SomeOtherProperty).SequenceEqual(GetAllA(main2).Select(a => a.SomeProperty + a.SomeOtherProperty))); // No assert } } 原始答案 首先,您可以使用 其次,如果设置为 考虑以下测试用例: public class B { public int Index { get; set; } } public class A { static int nextId = -1; readonly B [] items; // A private read-only array that is never changed. [JsonConstructor] private A(IEnumerable<B> Items,string SomeProperty) { this.items = (Items ?? Enumerable.Empty<B>()).ToArray(); this.SomeProperty = SomeProperty; } // // Create instances of "B" with different properties each time the default constructor is called. public A() : this(Enumerable.Range(101 + 10*Interlocked.Increment(ref nextId),2).Select(i => new B { Index = i }),"foobar") { } public IEnumerable<B> Items { get { foreach (var b in items) yield return b; } } [JsonIgnore] public int Count { get { return items.Length; } } public B GetItem(int index) { return items[index]; } public string SomeProperty { get; set; } public string SomeOtherProperty { get; set; } } public class TestClass { public A A { get; set; } public List<B> ListOfB { get; set; } public static void Test() { var a = new A() { SomeOtherProperty = "something else" }; var test = new TestClass { A = a,ListOfB = a.Items.Reverse().ToList() }; var settings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects }; var json = JsonConvert.SerializeObject(test,settings); Debug.WriteLine(json); var test2 = JsonConvert.DeserializeObject<TestClass>(json,settings); // Assert that pointers in "ListOfB" are equal to pointers in A.Items Debug.Assert(test2.ListOfB.All(i2 => test2.A.Items.Contains(i2,new ReferenceEqualityComparer<B>()))); // Assert deserialized data is the same as the original data. Debug.Assert(test2.A.SomeProperty == test.A.SomeProperty); Debug.Assert(test2.A.SomeOtherProperty == test.A.SomeOtherProperty); Debug.Assert(test2.A.Items.Select(i => i.Index).SequenceEqual(test.A.Items.Select(i => i.Index))); var json2 = JsonConvert.SerializeObject(test2,settings); Debug.WriteLine(json2); Debug.Assert(json2 == json); } } 在这种情况下,我创建了包含一些数据的类B,包含它在公共构造函数中创建的B的不可变集合的类A,以及包含A实例和从A中获取的项B列表的包含类TestClass.当我序列化这个时,我得到以下JSON:
然后,当我反序列化它时,我断言ListOfB中的所有反序列化项B都与a.Items中B的一个实例具有指针相等性.我还断言所有反序列化的属性都与原始属性具有相同的值,从而确认调用了非默认的私有构造函数来反序列化不可变集合. 这是你想要的吗? 为了检查B实例的指针相等性,我使用: public class ReferenceEqualityComparer<T> : IEqualityComparer<T> where T : class { #region IEqualityComparer<T> Members public bool Equals(T x,T y) { return object.ReferenceEquals(x,y); } public int GetHashCode(T obj) { return (obj == null ? 0 : obj.GetHashCode()); } #endregion } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |