我正在尝试序列化和反序列化一个复杂的对象图:
类(class)A
包含一个只读属性,其中包含类型为 B
的不可变对象(immutable对象)数组。 . B
类型的对象以及不可变数组是在类型 A
的构造函数中创建的。 .
其他类型包含对类型 B
的对象的引用通过访问 A
类型的对象的数组获得.
在反序列化期间,我需要对 B
的任何引用。最终指向由 A
创建的适当对象通过索引构造函数,而不是创建全新的 B
来自 JSON 的对象。我正在尝试使用 PreserveReferencesHandling
使用 JSON.NET。这可以理解是行不通的,因为它试图使用 B
的反序列化版本。而不是 A
- 构建版本。
我可以在这里使用另一种策略而不修改我的类型吗?
编辑:为了澄清并非常清楚,解决方案不得修改类型本身。您可以触摸契约(Contract)解析器、活页夹、引用解析器等,但不能触摸类型。另外,B
类型不能反序列化。它们必须由 A
制作的构造函数。
最佳答案
更新
你的问题没有给出你想要完成的例子,所以我在猜测你的一些设计要求。确认一下,你的情况是:
A
的实例。 . A
包含类 B
的不可变实例数组只能在 A
的构造函数中构造. A
的每个实例可能有也可能没有要序列化的属性(未指定)B
的每个实例可能有也可能没有要序列化的属性(未指定)。 B
实例的引用。 ,但在所有情况下,这些引用实际上指向 B
的一个实例在 A
的实例之一内. B
实例的所有引用。 to 指向 B
的一个实例在 A
的实例中对应于原始实例,按数组索引。 A
的所有实例在您的对象图中。 让我们使用以下类对这种情况进行建模:
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
反序列化 A
的属性(如果有)和 B
, 和 adds a reference对于序列化 "$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
}
}
原答案
首先,您可以使用
[JsonConstructor]
属性指定 Json.NET 应使用非默认构造函数来反序列化您的类 A
.这样做将允许您反序列化为您的不可变集合。此构造函数可以是私有(private)的,以便您可以继续创建 B
的实例。在预先存在的公共(public)构造函数中。请注意,构造函数参数名称必须与原始属性名称匹配。其次,如果你设置
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, 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
有一些数据,类(class) A
其中包含一个不可变的集合 B
它在其公共(public)构造函数中创建,以及一个包含类 TestClass
包含 A
的实例和项目列表 B
取自 A
.当我序列化它时,我得到以下 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" } ] }
然后,当我反序列化它时,我断言所有反序列化的项目
B
在 ListOfB
与 B
的实例之一具有指针相等性在 a.Items
.我还断言所有反序列化的属性都具有与原始属性相同的值,从而确认调用了非默认私有(private)构造函数来反序列化不可变集合。这是你想要的吗?
用于检查
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
}
关于c# - 引用自动创建的对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30648718/