A类包含一个只读属性,包含B类型的不可变对象数组.B类型的对象以及不可变数组是在类型A的构造函数中创建的.
其他类型包含对通过访问类型A的对象的数组获得的类型B的对象的引用.
在反序列化期间,我需要对B的任何引用最终指向由A构造函数通过索引创建的适当对象,而不是从JSON创建全新的B对象.我正在尝试将PreserveReferencesHandling与JSON.NET一起使用.这是可行的,因为它试图使用B的反序列化版本而不是A构造的版本.
编辑:为了澄清并明确说明,解决方案不得修改类型本身.您可以触摸合约解析器,活页夹,参考解析器等,但不能触摸类型.此外,B类型无法反序列化.它们必须由A的构造函数构成.
解决方法
你的问题没有举例说明你想要完成什么,所以我猜你的一些设计要求.要确认,您的情况是:
>您有一些复杂的对象图,可以使用Json.NET进行序列化
>在整个图表中,有许多A类实例.
> A包含B类实例的不可变数组,只能在A的构造函数内构造.
> A的每个实例可能有也可能没有要序列化的属性(未指定)
> B的每个实例可能有也可能没有要序列化的属性(未指定).
>在整个图中,还有许多对B实例的引用,但在所有情况下,这些引用实际上都指向A的一个实例中的B实例.
>当您对图表进行反序列化时,您需要对B实例的所有引用指向与原始实例对应的A实例中的B实例,按数组索引.
>您没有任何代码可以收集和发现对象图中的所有A实例.
>您无法以任何方式触摸类的c#代码,甚至无法添加数据协定属性或私有属性.
让我们用以下类来模拟这种情况:
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 = PreserveReferencesHandling.Objects
的情况下,将包含所有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 } }
原始答案
首先,您可以使用[JsonConstructor]
属性指定Json.NET应使用非默认构造函数来反序列化您的类A.这样做将允许您反序列化到不可变集合中.此构造函数可以是私有的,因此您可以继续在预先存在的公共构造函数中创建B的实例.请注意,构造函数参数名称必须与原始属性名称匹配.
其次,如果设置为PreserveReferencesHandling = PreserveReferencesHandling.Objects
,则对象图中直接引用由不可变数组保存的B实例的任何其他对象在序列化和反序列化时将继续直接引用反序列化不可变数组中的实例.即,它应该工作.
考虑以下测试用例:
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:
06003
然后,当我反序列化它时,我断言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 }
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。