Is there any way to force newtonsoft to swap a type when it encounters a property of that type during serialization/deserialization?
If I have the following:
public class SomeClass
{
public SomeNonSerializableType Property { get; set; }
}
If during serialization I encounter a SomeNonSerializableType
I want to use Automapper to map it to a SomeSerializableType
. I'm assuming I can do this with a contract resolver but not really sure how to implement it. I would then need to do the same in reverse e.g if I encounter a SomeSerializableType
during deserialization, map it back to SomeNonSerializableType
.
You could create a custom generic JsonConverter
that automatically maps from some model to DTO type during serialization, and maps back during deserialization, like so:
public class AutomapperConverter<TModel, TDTO> : JsonConverter
{
static readonly Lazy<MapperConfiguration> DefaultConfiguration
= new Lazy<MapperConfiguration>(() => new MapperConfiguration(cfg => cfg.CreateMap<TModel, TDTO>().ReverseMap()));
public AutomapperConverter(MapperConfiguration config) => this.config = config ?? throw new ArgumentNullException(nameof(config));
public AutomapperConverter() : this(DefaultConfiguration.Value) { }
readonly MapperConfiguration config;
public override bool CanConvert(Type type) => type == typeof(TModel);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var dto = config.CreateMapper().Map<TDTO>(value);
serializer.Serialize(writer, dto);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var dto = serializer.Deserialize<TDTO>(reader);
return config.CreateMapper().Map(dto, dto.GetType(), objectType);
}
}
And then add concrete instances to JsonSerializerSettings.Converters
like so:
// Configure this statically on startup
MapperConfiguration configuration
= new MapperConfiguration(cfg =>
{
// Add all your mapping configurations here.
// ReverseMap() ensures you can map from and to the DTO
cfg.CreateMap<SomeNonSerializableType, SomeSerializableType>().ReverseMap();
});
// Set up settings using the global configuration.
var settings = new JsonSerializerSettings
{
Converters = { new AutomapperConverter<SomeNonSerializableType, SomeSerializableType>(configuration) },
};
var json = JsonConvert.SerializeObject(someClass, Formatting.Indented, settings);
var deserialized = JsonConvert.DeserializeObject<SomeClass>(json, settings);
Notes:
The converter is not intended to handle polymorphism.
The converter does not support preservation of object references, but could be extended to do so by using JsonSerializer.ReferenceResolver
as shown in JsonConverter resolve reference or Custom object serialization vs PreserveReferencesHandling.
The AutoMapper docs suggest:
Configuration should only happen once per AppDomain.
However the converter will configure a default mapping if none is passed in.
Demo fiddle here.