Reputation: 47
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
.
Upvotes: 1
Views: 1503
Reputation: 116670
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.
Upvotes: 2
Reputation: 514
I just had to do something similar yesterday. Although in my case I only needed to change property name mappings, you can change types with JsonConverter
like so:
public enum UserStatus
{
NotConfirmed,
Active,
Deleted
}
public class User
{
public string UserName { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public UserStatus Status { get; set; }
}
According to the reference:
JsonConverterAttribute The JsonConverterAttribute specifies which JsonConverter is used to convert an object. The attribute can be placed on a class or a member. When placed on a class, the JsonConverter specified by the attribute will be the default way of serializing that class. When the attribute is on a field or property, then the specified JsonConverter will always be used to serialize that value. The priority of which JsonConverter is used is member attribute, then class attribute, and finally any converters passed to the JsonSerializer.
According to the class reference, the type is of System.Type.
It doesn't sound needed, but possibly also of interest may be the JsonConstructorAttribute
:
The JsonConstructorAttribute instructs the JsonSerializer to use a specific constructor when deserializing a class. It can be used to create a class using a parameterized constructor instead of the default constructor, or to pick which specific parameterized constructor to use if there are multiple:
public class User
{
public string UserName { get; private set; }
public bool Enabled { get; private set; }
public User()
{
}
[JsonConstructor]
public User(string userName, bool enabled)
{
UserName = userName;
Enabled = enabled;
}
}
Upvotes: 0