Итак, чтобы подвести итог вашего вопроса / комментариев:
- У вас есть огромная и сложная устаревшая структура данных, которую вы хотели бы сериализовать и десериализовать.
- Большая часть данных, которые выхотите, чтобы de / serialize находился в закрытых полях, и слишком сложно добавлять к ним атрибуты или добавлять открытые свойства.
- Некоторые из этих полей имеют тип
object
, и вы хотите сохранить тип этих значенийв оба конца. - Некоторые из этих полей специально помечены как игнорируемые для целей сериализации, и у них есть значения по умолчанию, которые вы хотели бы сохранить.
- Вы пытались использовать
TypeNameHandling.Auto
и осуществляетеISerializable
;это работало для сериализации (тип дочернего объекта был записан в JSON), но не работало для десериализации (вы получили JObject
вместо реального экземпляра дочернего объекта). - Вы пытались использовать
TypeNameHandling.Auto
и маркировка вашего внешнего класса MemberSerialization.Fields
;это работало для сериализации, но при десериализации вы потеряли значения по умолчанию для ваших игнорируемых полей.
Давайте рассмотрим оба подхода и посмотрим, что мы можем сделать.
ISerializable
Кажется немного странным, что Json.Net не соблюдает настройку TypeNameHandling.Auto
при десериализации для дочерних объектов ISerializable
, когда записывает информацию о типах для них в JSON при сериализации.Я не знаю, является ли это ошибкой, недосмотром или здесь есть какие-то технические ограничения.Несмотря на это, он не ведет себя так, как ожидалось.
Однако вы можете обойти эту проблему.Поскольку вы получаете JObject
обратно в SerializationInfo
, а в JObject
есть строка $type
, вы можете создать метод расширения, который создаст ваш дочерний объект из JObject
на основе $type
:
public static class SerializationInfoExtensions
{
public static object GetObject(this SerializationInfo info, string name)
{
object value = info.GetValue(name, typeof(object));
if (value is JObject)
{
JObject obj = (JObject)value;
string typeName = (string)obj["$type"];
if (typeName != null)
{
Type type = Type.GetType(typeName);
if (type != null)
{
value = obj.ToObject(type);
}
}
}
return value;
}
}
Затем используйте его в конструкторе сериализации вместо GetValue
всякий раз, когда типом является object
:
public Foobar(SerializationInfo info, StreamingContext context)
{
something = info.GetObject("something");
}
MemberSerialization.Fields
Похоже, что использование атрибута [JsonObject(MemberSerialization = MemberSerialization.Fields)]
в основном делает то, что вы хотите, за исключением потери значений по умолчанию для некоторых из ваших игнорируемых свойств.Оказывается, что Json.Net намеренно не вызывает нормальный конструктор при использовании MemberSerialization.Fields
- вместо этого он использует метод FormatterServices.GetUninitializedObject
для создания полностью пустого объекта перед заполнениемполя из JSON.Это, очевидно, предотвратит инициализацию значений по умолчанию.
Чтобы обойти это, вы можете использовать пользовательский ContractResolver
для замены функции создания объекта, например:
class FieldsOnlyResolver : DefaultContractResolver
{
protected override JsonObjectContract CreateObjectContract(Type objectType)
{
JsonObjectContract contract = base.CreateObjectContract(objectType);
contract.DefaultCreator = () => Activator.CreateInstance(objectType, true);
return contract;
}
}
Примечание:Выше предполагается, что все ваши объекты будут иметь конструкторы по умолчанию (без параметров).Если это не так, вы можете добавить их в случае необходимости (они могут быть частными) или изменить преобразователь для предоставления другой функции создателя в зависимости от типа.
Чтобы использовать его, просто добавьте преобразовательна ваш JsonSerializer
экземпляр:
serializer.ContractResolver = new FieldsOnlyResolver();