Code: Select all
internal interface IBase : ISerializable
{
int Number { get; }
}
internal interface ITable : IBase
{
ISection Section { get; }
}
[Serializable]
internal class Table : ITable
{
public int Number { get; private init; }
public ISection Section { get; internal set; }
private Table()
{ }
public Table(int number)
{
Number = number;
}
private Table(SerializationInfo info, StreamingContext context)
{
Number = info.GetInt32(nameof(Number));
Section = (ISection)info.GetValue(nameof(Section), typeof(ISection));
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(Number), Number);
info.AddValue(nameof(Section), Section);
}
}
internal interface ISection : IBase
{
IReadOnlyList Tables { get; }
}
[Serializable]
internal class Section : ISection
{
public int Number { get; private init; }
public IReadOnlyList Tables { get; internal set; }
private Section()
{ }
public Section(int number)
{
Number = number;
}
private Section(SerializationInfo info, StreamingContext context)
{
Number = info.GetInt32(nameof(Number));
Tables = (IReadOnlyList)info.GetValue(nameof(Tables), typeof(IReadOnlyList));
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(Number), Number);
info.AddValue(nameof(Tables), Tables);
}
}
Und ich erstelle (mit einer Referenzschleife) und serialisiere sie auf folgende Weise:
Code: Select all
var table = new Table(1);
var section = new Section(1);
table.Section = section;
section.Tables = new List { table };
var settings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
TypeNameHandling = TypeNameHandling.All,
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
ContractResolver = new CustomContractResolver() { IgnoreSerializableInterface = true }
};
var json = JsonConvert.SerializeObject(section, settings);
var restored = JsonConvert.DeserializeObject(json, settings);
Code: Select all
internal class CustomContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
prop.Readable = true;
if (!prop.Writable)
{
switch (member.MemberType)
{
case MemberTypes.Field:
prop.Writable = true;
break;
case MemberTypes.Property:
var hasPrivateSetter = (member as PropertyInfo)!.GetSetMethod(true) != null;
prop.Writable = hasPrivateSetter;
break;
}
}
return prop;
}
protected override List GetSerializableMembers(Type objectType)
{
var members = base.GetSerializableMembers(objectType);
var nonPublicFields = objectType.GetFields(
BindingFlags.Instance |
BindingFlags.NonPublic)
.Where(f => !f.Name.EndsWith("k__BackingField")).ToList(); // excluding backing fields from auto-properties
members.AddRange(nonPublicFields);
var nonPublicProperties = objectType.GetProperties(
BindingFlags.Instance |
BindingFlags.NonPublic).ToList();
members.AddRange(nonPublicProperties);
return members;
}
}
Mein Ziel ist es, weiterhin GetObjectData für die Serialisierung zu verwenden, aber den Standardkonstruktor für die Deserialisierung (zur Wiederherstellung von Referenzen) zu verwenden. Ich habe das IgnoreSerializableInterface = true-Flag entfernt und einen benutzerdefinierten Konverter hinzugefügt:
Code: Select all
internal class ISerializableConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(ISerializable).IsAssignableFrom(objectType);
}
public override bool CanRead => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException("Deserialization is not used with this converter");
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var serializableObject = (ISerializable)value;
var info = new SerializationInfo(value.GetType(), new FormatterConverter());
var context = new StreamingContext(StreamingContextStates.All);
serializableObject.GetObjectData(info, context);
writer.WriteStartObject();
foreach (SerializationEntry entry in info)
{
writer.WritePropertyName(entry.Name);
serializer.Serialize(writer, entry.Value);
}
writer.WriteEndObject();
}
}
internal class CustomContractResolver : DefaultContractResolver
{
...
//I use ISerializableConverter here
protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
if (contract is JsonISerializableContract)
{
//for deserialization we use constructor that was found by CreateObjectContract()
var objectContract = CreateObjectContract(objectType);
// setting converter for serilaizing via GetObjectData
objectContract.Converter = new ISerializableConverter();
return objectContract;
}
return contract;
}
...
}
Newtonsoft.Json.JsonSerializationException: 'Self referencing loop
detected with type 'NewtonsoftJsonTest.Section'. Pfad
'Tables.$values[0]'.'
Übrigens kann ich meinen Klassen keine Newtonsoft-Attribute hinzufügen.
Ist eine Implementierung also möglich?
Mobile version