Newtonsoft для классов ISerializable со ссылочным циклом — используйте GetObjectData для сериализации и обычный конструкC#

Место общения программистов C#
Ответить
Anonymous
 Newtonsoft для классов ISerializable со ссылочным циклом — используйте GetObjectData для сериализации и обычный конструк

Сообщение Anonymous »

У меня есть пара классов ISerializable (упрощенно):

Код: Выделить всё

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);
}
}
Как вы можете видеть, классы имеют частный конструктор по умолчанию и установщики частных/внутренних свойств.
И я создаю (с помощью ссылочного цикла) и сериализую их следующим образом:

Код: Выделить всё

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);
В моем CustomContractResolver я собираю всех непубличных членов:

Код: Выделить всё

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;
}
}
Это работает, но полностью позволяет избежать как GetObjectData, так и конструктора с информацией SerializationInfo, контекста StreamingContext.
Моя цель — продолжать использовать GetObjectData для сериализации, но использовать конструктор по умолчанию для десериализации (для восстановления ссылок). Я удалил флаг IgnoreSerializableInterface = true и добавил собственный конвертер:

Код: Выделить всё

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: «Цикл самоссылки
обнаружен с типом «NewtonsoftJsonTest.Section». Path
'Tables.$values[0]'.'

Кстати, я не могу добавлять атрибуты Newtonsoft в свои классы.
Так возможно ли это реализовать?

Подробнее здесь: https://stackoverflow.com/questions/798 ... jectdata-f
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»