引用自动创建的对象 [英] Reference to automatically created objects

查看:70
本文介绍了引用自动创建的对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am attempting to serialize and deserialize a complex object graph:

A包含一个只读属性,该属性包含类型为B的对象的不可变数组. B类型的对象以及不可变数组在A类型的构造函数中创建.

Class A contains an read only property containing an immutable array of objects of type B. The objects of type B, as well as the immutable array, are created in the constructor of type A.

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

Other types contain references to objects of type B that are obtained by accessing the array of an object of type A.

反序列化期间,我需要任何对B的引用来最终指向由A构造函数通过索引创建的适当对象,而不是从JSON生成全新的B对象.我正在尝试将PreserveReferencesHandling与JSON.NET结合使用.可以理解,这是行不通的,因为它尝试使用B的反序列化版本,而不是A构造的版本.

During deserialization, I need any references to a B to end up pointing at the appropriate object created by the A constructor by index, rather than making brand new B objects from JSON. I'm trying to use PreserveReferencesHandling with JSON.NET. This understandably does not work, because it attempts to use deserialized versions of B rather than the A-constructed versions.

在这里我还可以使用另一种无需修改类型的策略吗?

Is there another strategy I can use here without modifying my types?

为明确起见,解决方案不得修改类型本身.您可以触摸合同解析器,活页夹,参考解析器等,但不能触摸类型.另外,不能对B类型的 进行反序列化.它们必须由A的构造函数完成.

To clarify and make extremely clear, the solution must not modify the type itself. You can touch the contract resolver, binder, reference resolver, etc. but not the type. Also, B types cannot be deserialized. They must be made by A's constructor.

推荐答案

更新

您的问题没有给出您要完成的工作的示例,因此我正在猜测您的一些设计要求.确认一下,您的情况是:

Your question doesn't give an example of what you are trying to accomplish, so I'm guessing about some of your design requirements. To confirm, your situation is:

  1. 您有一些复杂的对象图可通过Json.NET进行序列化
  2. 在整个图中,有许多类A的实例.
  3. A包含类B 的不可变数组,这些实例只能在A 的构造函数中构造.
  4. 每个A实例可能具有或不具有要序列化的属性(未指定)
  5. 每个B实例可能具有或不具有序列化的属性(未指定).
  6. 在整个图中,有很多引用B的实例,但是在所有情况下,这些引用实际上都指向A 实例之一内的B实例.
  7. 反序列化图形时,需要所有对B实例的引用,以通过数组索引指向与原始实例相对应的A实例内部的B实例.
  8. 您没有任何代码可以收集和发现对象图中A的所有实例.
  9. 您不能以任何方式触摸类的c#代码,甚至不能添加数据协定属性或私有属性.
  1. You have some complex graph of objects to serialize with Json.NET
  2. Throughout the graph, there are many instances of class A.
  3. A contains an immutable array of instances of class B that can only ever be constructed inside the constructor of A.
  4. Each instance of A might or might not have properties to serialize (not specified)
  5. Each instance of B might or might not have properties to serialize (not specified).
  6. Also throughout the graph there are many references to instances of B, but in all cases these references actually point to an instance of B inside one of the instances of A.
  7. When you deserialize your graph, you need all references to an instance of B to to point to an instance of B inside an instance of A corresponding to the original instance, by array index.
  8. You don't have any code to collect and discover all instances of A in your object graph.
  9. You cannot touch the c# code for the classes in any way, not even to add data contract attributes or private properties.

让我们使用以下类对这种情况进行建模:

Let's model this situation with the following classes:

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的所有实例作为 first 项,然后将根对象作为 second 项.

Then, to serialize, you need to use Json.NET to collect all instances of A in your object graph. Next, with PreserveReferencesHandling = PreserveReferencesHandling.Objects set, serialize a proxy class containing a table of all instances of A as the first item, then your root object as the second item.

要反序列化,请使用PreserveReferencesHandling.ObjectsAJsonConverter使用JsonConverter来反序列化代理类,该序列会反序列化AB的属性(如果有)以及引用添加引用A的构造函数中.

To deserialize, with PreserveReferencesHandling.Objects you must deserialize your proxy class using a JsonConverter for A that deserializes properties (if any) of A and B, and adds a reference for the serialized "$ref" references to B to the new instances of B allocated in the constructor of 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
    }
}

原始答案

首先,您可以使用 [JsonConstructor] 属性来指定Json.NET应该使用非默认构造函数反序列化您的类A.这样做可以使您反序列化为不可变的集合.该构造函数可以是私有的,因此您可以继续在预先存在的公共构造函数中创建B的实例.请注意,构造函数参数名称必须与原始属性名称匹配.

Firstly, you can use the [JsonConstructor] attribute to specify that Json.NET should use a non-default constructor to deserialize your class A. Doing so will allow you to deserialize into your immutable collection. This constructor can be private, so that you can continue to create your instances of B in the pre-existing public constructor. Note that the constructor argument names must match the original property names.

第二,如果您设置 PreserveReferencesHandling = PreserveReferencesHandling.Objects ,则可以设置其他任何对象图中的直接引用不可变数组所保存的B实例的对象在进行序列化和反序列化后,将继续直接引用反序列化不可变数组中的实例.也就是说,它应该可以正常工作.

Secondly, if you set PreserveReferencesHandling = PreserveReferencesHandling.Objects, then any other objects in your object graph that refer directly to instances of B held by the immutable array will, when serialized and deserialized, continue to refer directly to the instances in the deserialized immutable array. I.e., it should just work.

请考虑以下测试案例:

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);
    }
}

在这种情况下,我用一些数据创建了类B,类A包含了在公共构造函数中创建的B的不可变集合,并且包含了包含以下内容的实例的类TestClass A和从A获取的项目B的列表.当我对其进行序列化时,将获得以下JSON:

In this case I have created class B with some data, class A which contains an immutable collection of B which it creates in its public constructor, and an encompassing class TestClass that contains an instance of A and a list of items B taken from A. When I serialize this, I get the following JSON:

{
  "$id": "1",
  "A": {
    "$id": "2",
    "Items": [
      {
        "$id": "3",
        "Index": 101
      },
      {
        "$id": "4",
        "Index": 102
      }
    ],
    "SomeProperty": "foobar",
    "SomeOtherProperty": "something else"
  },
  "ListOfB": [
    {
      "$ref": "4"
    },
    {
      "$ref": "3"
    }
  ]
}

然后,当我对其进行反序列化时,我断言ListOfB中的所有反序列化项Ba.Items中的B实例之一具有指针相等性.我还断言所有反序列化的属性都具有与原始序列中相同的值,从而确认调用了非默认的私有构造函数来反序列化不可变的集合.

Then, when I deserialize it, I assert that all deserialized items B in ListOfB have pointer equality with one of the instances of B in a.Items. I also assert that all deserialized properties have the same values as in the originals, thus confirming that the non-default private constructor was called to deserialize the immutable collection.

这是您想要的吗?

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

For checking pointer equality of instances of B, I use:

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
}

这篇关于引用自动创建的对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆