Ваша основная проблема заключается в том, что вы предоставляете custom JsonConverter
для типа, для которого вы также хотите включить PreserveReferencesHandling
.Но всякий раз, когда применяется пользовательский конвертер, он должен позаботиться о все вручную, , включая разбор и генерацию "$ref"
и "$id"
свойств .Ваш конвертер этого не делает, поэтому ваш код десериализации неправильно десериализует ваш граф объектов.
принятый ответ на Сериализация пользовательских объектов против PreserveReferencesHandling включает конвертер шаблонов, который показывает, как эти свойства могут быть обработаны.Однако, поскольку ваш конвертер, похоже, переключает некоторые настройки сериализатора до (де) сериализации, вы можете просто сделать вызов, чтобы рекурсивно (де) сериализовать объект, отключив конвертер на время, например:
public class CustomJsonConverter : Newtonsoft.Json.JsonConverter
{
[ThreadStatic]
static bool disabled;
// Disables the converter in a thread-safe manner.
bool Disabled { get { return disabled; } set { disabled = value; } }
public override bool CanWrite { get { return !Disabled; } }
public override bool CanRead { get { return !Disabled; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
using (new PushValue<bool>(true, () => Disabled, val => Disabled = val))
using (new PushValue<PreserveReferencesHandling>(PreserveReferencesHandling.Objects, () => serializer.PreserveReferencesHandling, val => serializer.PreserveReferencesHandling = val))
using (new PushValue<DefaultValueHandling>(DefaultValueHandling.Ignore, () => serializer.DefaultValueHandling, val => serializer.DefaultValueHandling = val))
using (new PushValue<NullValueHandling>(NullValueHandling.Ignore, () => serializer.NullValueHandling, val => serializer.NullValueHandling = val))
{
serializer.Serialize(writer, value);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
using (new PushValue<bool>(true, () => Disabled, val => Disabled = val))
using (new PushValue<PreserveReferencesHandling>(PreserveReferencesHandling.Objects, () => serializer.PreserveReferencesHandling, val => serializer.PreserveReferencesHandling = val))
using (new PushValue<DefaultValueHandling>(DefaultValueHandling.Ignore, () => serializer.DefaultValueHandling, val => serializer.DefaultValueHandling = val))
using (new PushValue<NullValueHandling>(NullValueHandling.Ignore, () => serializer.NullValueHandling, val => serializer.NullValueHandling = val))
{
return serializer.Deserialize(reader, objectType);
}
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
public struct PushValue<T> : IDisposable
{
Action<T> setValue;
T oldValue;
public PushValue(T value, Func<T> getValue, Action<T> setValue)
{
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
setValue(value);
}
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (setValue != null)
setValue(oldValue);
}
}
Обратите внимание, что логика отключения конвертера должна быть поточно-ориентированной, поскольку Json.NET будет совместно использовать контракты и конвертеры между потоками.
Демонстрационная скрипта # 1 здесь .
В качестве альтернативы вы можете полностью исключить преобразователь и применить [JsonObject(IsReference = true)]
непосредственно к ModelBase
:
[JsonObject(IsReference = true)]
public abstract class ModelBase : IModelBase
{
public string ID { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ModifiedAt { get; set; }
}
Затем сериализовать и десериализоватьс DefaultValueHandling.Ignore
и NullValueHandling.Ignore
, указанными в настройках следующим образом:
static object Deserialize(Stream streamFromWebAPICall, Type objectType)
{
using (var sr = new StreamReader(streamFromWebAPICall))
{
using (var jtr = new JsonTextReader(sr))
{
var settings = new JsonSerializerSettings
{
DefaultValueHandling = DefaultValueHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
};
var js = JsonSerializer.CreateDefault(settings);
return js.Deserialize(jtr, objectType);
}
}
}
Демонстрационная скрипка # 2 здесь .
Обратите внимание, вы также можете установить [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
по состоянию на Json.NET 11.0.1 , но, по-видимому, нет настройки ItemDefaultValueHandling
для JsonObjectAttribute
, поэтому добавьте ее в настройки сериализатора (или используйте обнуляемые значения для необязательного значения)значения типа, такие как CreatedAt
).