Десериализация через Newtonsoft для классов с ссылочным циклом, частными конструкторами и установщиками частных/внутреннC#

Место общения программистов C#
Ответить
Anonymous
 Десериализация через Newtonsoft для классов с ссылочным циклом, частными конструкторами и установщиками частных/внутренн

Сообщение Anonymous »

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

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

internal interface ITable
{
int Number { get; }
ISection Section { get; }
}

internal class Table : ITable
{
public int Number { get; private init; }
public ISection Section { get; internal set; }

private Table(int number)
{
Number = number;
}

internal static Table CreateTable(int number)
{
return new Table(number);
}
}

internal interface ISection
{
int Number { get; }
IReadOnlyList Tables { get; }
}

internal class Section : ISection
{
public int Number { get; private init; }
public IReadOnlyList Tables { get; internal set; }

private Section(int number)
{
Number = number;
}

internal static Section CreateSection(int number)
{
return new Section(number);
}
}
Как вы можете видеть, классы имеют частные конструкторы и установщики частных/внутренних свойств.
И я создаю (с помощью ссылочного цикла) и сериализую их следующим образом:

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

var table = Table.CreateTable(1);
var section = Section.CreateSection(1);
table.Section = section;
section.Tables = new List { table };

var settings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
TypeNameHandling = TypeNameHandling.All,
ContractResolver = new CustomContractResolver()
};

var json = JsonConvert.SerializeObject(section, settings);
var restored = JsonConvert.DeserializeObject(json, settings);
В моем CustomContractResolver я собираю всех непубличных членов, а также, если Newtonsoft не смог найти подходящий конструктор, я помогаю ему найти непубличный конструктор:

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

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;
}

protected override JsonObjectContract CreateObjectContract(Type objectType)
{
var contract = base.CreateObjectContract(objectType);

// if Newtonsoft.Json was not able to find suitable constructor - helping him to find a non public constructor
if (contract.OverrideCreator == null && contract.DefaultCreator == null && !objectType.IsInterface && !objectType.IsAbstract)
{
var constructor = objectType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.FirstOrDefault(c => c.GetParameters().Length >  0);

if (constructor != null)
{
var creatorParameters = new List();

foreach (var param in constructor.GetParameters())
{
var prop = contract.Properties.GetClosestMatchProperty(param.Name);

if (prop != null)
{
creatorParameters.Add(new JsonProperty
{
PropertyName = prop.PropertyName,
PropertyType = param.ParameterType,
DeclaringType = constructor.DeclaringType,
ValueProvider = prop.ValueProvider
});
}
else
{
creatorParameters = null;
break;
}
}

if (creatorParameters != null)
{
contract.OverrideCreator = constructor.Invoke;
contract.CreatorParameters.Clear();
foreach (var param in creatorParameters)
{
contract.CreatorParameters.Add(param);
}
}
}
}

return contract;
}
}
Но после десериализацииsection.Tables[0].Section=null (похоже, опция PreserveReferencesHandling не сработала так, как я ожидал):
Изображение

Если я добавлю общедоступное значение по умолчанию конструктор для Таблицы и Раздела - работает как положено (это всего лишь эксперимент - я не могу менять классы данных и добавлять конструкторы). Похоже, что-то не так с моим методом CreateObjectContract.
В чем моя ошибка?

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

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

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

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

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

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