引用自动创建的对象

时间:2015-06-04 16:00:21

标签: c# json.net

我正在尝试序列化和反序列化复杂的对象图:

.background-image { background: url(./../imgs/beach.png) no-repeat; background-size: 100%; height: 100vh; } 包含一个只读属性,其中包含类型为A的不可变对象数组。 B类型的对象以及不可变数组是在B类型的构造函数中创建的。

其他类型包含对A类型的对象的引用,这些对象是通过访问B类型的对象的数组获得的。

在反序列化期间,我需要对A的任何引用最终指向由B构造函数按索引创建的相应对象,而不是从JSON创建全新的A对象。我试图将B与JSON.NET一起使用。这是可行的,因为它试图使用PreserveReferencesHandling的反序列化版本而不是B构造的版本。

我可以在不修改类型的情况下使用其他策略吗?

编辑:为了澄清并明确说明,解决方案不得修改类型本身。您可以触摸合约解析器,活页夹,参考解析器等,但不能触摸类型。此外,A类型无法反序列化。它们必须由B的构造函数构成。

1 个答案:

答案 0 :(得分:1)

<强>更新

您的问题并没有举例说明您要完成的任务,因此我猜测您的一些设计要求。要确认,您的情况是:

  1. 您有一些使用Json.NET序列化的对象的复杂图表
  2. 在整个图表中,有许多类A的实例。
  3. A包含类B 的不可变实例数组,只能在A 的构造函数内构建。
  4. A的每个实例可能有也可能没有要序列化的属性(未指定)
  5. B的每个实例可能有也可能没有要序列化的属性(未指定)。
  6. 同样在整个图表中有许多对B实例的引用,但在所有情况下,这些引用实际上指向B的一个实例中的A实例}
  7. 反序列化图表时,您需要对B实例的所有引用指向与原始实例对应的B实例内的A实例,数组索引。
  8. 您没有任何代码可以收集和发现对象图中A的所有实例。
  9. 您无法以任何方式触及类的c#代码,甚至无法添加数据协定属性或私有属性。
  10. 让我们用以下类来模拟这种情况:

    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实例的表的代理类序列化为第一个项,然后将根对象作为第二个 item。

    要使用PreserveReferencesHandling.Objects进行反序列化,必须使用JsonConverter A反序列化代理类,以A反序列化B"$ref"的属性(如果有) ,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, Type objectType, object existingValue, 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, object value, 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 } } 的构造函数中分配的新A实例。

    因此:

    B

    原始答案

    首先,您可以使用adds a reference属性指定Json.NET应使用非默认构造函数来反序列化您的类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, Formatting.Indented, 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, Formatting.Indented, settings); Debug.WriteLine(json2); Debug.Assert(json2 == json); } } 的实例。请注意,构造函数参数名称必须与原始属性名称匹配。

    其次,如果设置[JsonConstructor],那么对象图中直接引用由不可变数组保存的B实例的任何其他对象,在序列化和反序列化时,将继续直接引用反序列化的不可变数组中的实例。即,它应该工作。

    考虑以下测试用例:

    A

    在这种情况下,我创建了一个包含一些数据的类B,类TestClass包含它在公共构造函数中创建的A的不可变集合,以及一个包含类B 1}}包含A的实例和取自{ "$id": "1", "A": { "$id": "2", "Items": [ { "$id": "3", "Index": 101 }, { "$id": "4", "Index": 102 } ], "SomeProperty": "foobar", "SomeOtherProperty": "something else" }, "ListOfB": [ { "$ref": "4" }, { "$ref": "3" } ] } 的项B的列表。当我序列化时,我得到以下JSON:

    ListOfB

    然后,当我反序列化它时,我断言B中的所有反序列化项a.Items都与Bpublic 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 } 的一个实例具有指针相等性。我还断言所有反序列化的属性都与原始属性具有相同的值,从而确认调用了非默认的私有构造函数来反序列化不可变集合。

    这是你想要的吗?

    为了检查/*实例的指针相等性,我使用:

    /images/*