Update
Your question does not provide an example of what you are trying to accomplish, so I guess some of your design requirements. To confirm your situation:
- You have a complex graph of objects for serialization using Json.NET
- There are many instances of the class throughout the graph
A. Acontains an immutable array of class instances Bthat can only ever be built inside the constructor A.- Each instance
Amay or may not have properties for serialization (not specified) - Each instance
Bmay or may not have properties for serialization (not specified). B, B A.B, B A, , .A .- # , .
:
public abstract class B
{
public int Index { get; set; }
}
public class A
{
public class BActual : B
{
}
static int nextId = -1;
readonly B[] items;
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 "$ref" B B, A.
:
public class NullJsonWriter : JsonWriter
{
public NullJsonWriter()
: base()
{
}
public override void Flush()
{
}
}
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);
}
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");
using (var objReader = obj.CreateReader())
serializer.Populate(objReader, a);
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");
foreach (var pair in a.Items.Zip(itemsArray, (b, o) => new { ItemB = b, JObj = o }))
{
#if false
var id = (string)pair.JObj["$id"];
if (id != null)
serializer.ReferenceResolver.AddReference(serializer, id, pair.ItemB);
#else
using (var objReader = pair.JObj.CreateReader())
{
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)
{
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);
}
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)
{
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));
Debug.Assert(json == json2);
Debug.Assert(main.ListOfB.Select(b => b.Index).SequenceEqual(main2.ListOfB.Select(b => b.Index)));
Debug.Assert(GetAllA(main).Select(a => a.SomeProperty + a.SomeOtherProperty).SequenceEqual(GetAllA(main2).Select(a => a.SomeProperty + a.SomeOtherProperty)));
}
}
-, [JsonConstructor], , Json.NET A. . , B . , .
-, PreserveReferencesHandling = PreserveReferencesHandling.Objects, , B, , , . I.e., .
:
public class B
{
public int Index { get; set; }
}
public class A
{
static int nextId = -1;
readonly B [] items;
[JsonConstructor]
private A(IEnumerable<B> Items, string SomeProperty)
{
this.items = (Items ?? Enumerable.Empty<B>()).ToArray();
this.SomeProperty = SomeProperty;
}
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);
Debug.Assert(test2.ListOfB.All(i2 => test2.A.Items.Contains(i2, new ReferenceEqualityComparer<B>())));
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, , 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. , , , , .
, ?
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
}