我正在尝试序列化和反序列化复杂的对象图:
类.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
的构造函数构成。
答案 0 :(得分:1)
<强>更新强>
您的问题并没有举例说明您要完成的任务,因此我猜测您的一些设计要求。要确认,您的情况是:
A
的实例。A
包含类B
的不可变实例数组,只能在A
的构造函数内构建。A
的每个实例可能有也可能没有要序列化的属性(未指定)B
的每个实例可能有也可能没有要序列化的属性(未指定)。B
实例的引用,但在所有情况下,这些引用实际上指向B
的一个实例中的A
实例} B
实例的所有引用指向与原始实例对应的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
实例的表的代理类序列化为第一个项,然后将根对象作为第二个 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
都与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
}
的一个实例具有指针相等性。我还断言所有反序列化的属性都与原始属性具有相同的值,从而确认调用了非默认的私有构造函数来反序列化不可变集合。
这是你想要的吗?
为了检查/*
实例的指针相等性,我使用:
/images/*