Newtonsoft für ISerialisierbare Klassen mit einer Referenzschleifenverwendung – verwenden Sie GetObjectData für die SeriC#

Ein Treffpunkt für C#-Programmierer
Anonymous
 Newtonsoft für ISerialisierbare Klassen mit einer Referenzschleifenverwendung – verwenden Sie GetObjectData für die Seri

Post by Anonymous »

Ich habe ein paar ISerializable-Klassen (vereinfacht):

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);
}
}
Wie Sie sehen können, haben Klassen einen privaten Standardkonstruktor und private/interne Eigenschaftssetzer.
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);
In meinem CustomContractResolver sammle ich alle nicht öffentlichen Mitglieder:

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;
}
}
Dies funktioniert, vermeidet jedoch vollständig sowohl GetObjectData als auch den Konstruktor mit SerializationInfo-Informationen und dem StreamingContext-Kontext.
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;
}
...
}
Aber bei diesem Ansatz erhalte ich eine Fehlermeldung:

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?

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post