0

I am using JsonDerivedType for polymorphic models in .NET Core, and I need to map a hierarchy of complex model objects to a set of DTO objects for serialization. Specifically, I want to use DTOs with polymorphic types when creating or updating different types of Notification ( NotificationAlert, SafetyAlert, etc.).

[JsonPolymorphic(TypeDiscriminatorPropertyName = "Type")]
[JsonDerivedType(typeof(NotificationAlert), typeDiscriminator: "notificationAlert")]
[JsonDerivedType(typeof(SafetyAlert), typeDiscriminator: "safetyAlert")]
[JsonDerivedType(typeof(perationalAlert), typeDiscriminator: "operationalAlert")]
public abstract class Notification
{
    public int NotificationId { get; set; }
    public DateTime CreatedDate { get; set; } = DateTime.Now;
    public Department department { get; set; }

}


public class NotificationAlert : Notification
{
    public string AlertMessage { get; set; }
}

Currently, my NotificationAlert class is working like this, and the JSON is being serialized correctly:

{
  "NotificationId": 1,
  "CreatedDate": "2025-02-17T12:00:00",
  "Type": "notificationAlert",
  "AlertMessage": "This is an alert message",
  "Department": {
    "Id": 101,
    "Name": "HR"
  }
}

However, I want to use DTOs to better manage the structure and serialization of the notification. Here's the DTO I have: I tried to use AutoMapper, but I encountered the following error: Cannot create an instance of abstract type is there some specific mapping I should do to resolve this issue

public class NotificationCreateRequestDTO
{
     public DateTimeOffset DateTime { get; set; }
    public string Department { get; set; }
    public string Type { get; set; }
    public string AlertMessage { get; set; }

}

And the expected JSON for this DTO:

{
  "DateTime": "2025-02-17T12:00:00+00:00",
  "Department": "HR",
  "Type": "notificationAlert",
  "AlertMessage": "This is an alert message"
}

Here are the mappings I set up:


CreateMap<NotificationCreateRequestDTO, Notification>()
    .Include<NotificationCreateRequestDTO, NotificationAlert>()
    .ForMember(dest => dest.NotificationId, opt => opt.MapFrom(src => src.NotificationId))
    .ForMember(dest => dest.CreatedDate, opt => opt.MapFrom(src => src.CreatedDate))
    .ForMember(dest => dest.Department, opt => opt.MapFrom(src => src.Department));  

CreateMap<NotificationAlert, NotificationCreateRequestDTO>()
    .ForMember(dest => dest.Notification, opt => opt.MapFrom(src => src))
    .ForMember(dest => dest.Department, opt => opt.MapFrom(src => src.Department));  

3
  • It is not clear what the actual question is. It looks like you already found the attributes needed for polymorphic serialization. You show two apparently unrelated types, neither have any logic, so both could be called a "DTO", but only one has such a postfix. You mention "alert types" but no such type is shown. What have you tried? what was the result? what result did you expect? Commented Feb 17 at 9:17
  • 1
    Or is this about mapping a hierarchy of complex model objects to a set of DTO objects for serialization? If so you should show a better example of the problem. Commented Feb 17 at 9:20
  • @JonasH Yes, this is about mapping a hierarchy of complex model objects to a set of DTO objects for serialization Commented Feb 17 at 9:36

2 Answers 2

1

There are a few options:

If your 'model' classes lack logic there is little to be gained from keeping and maintaining a separate set of dto classes. This might however indicate a separate problem, see Anemic domain model.

Next simplest option is to just add methods to manually map the model to the DTO and vice versa. A simplified example might look something like this:

public class AModel
{
    public long Id { get; set; }
    public virtual ADTO ToDTO() => new() { Id = Id };
}

public class BModel : AModel
{
    public string Name { get; set; }
    public override ADTO ToDTO() => new BDTO() { Id = Id, Name = Name };
}

[JsonDerivedType(typeof(BDTO), typeDiscriminator:"BDTO")]
public class ADTO
{
    public long Id { get; set; }
    public virtual AModel ToModel() => new() { Id = Id };
}
public class BDTO : ADTO
{
    public string Name { get; set; }
    public override AModel ToModel() => new BModel() { Id = Id, Name = Name };
}

If there is an inheritance hierarchy that needs to be preserved you should have one DTO class for each model class, and use the polymorphic serialization of the DTO classes, that should make the explicit 'type'-parameter unnecessary. Your mapping methods, i.e. ToDTO/ToModel need to be virtual to map to the correct type. You can do any kind of type transformation at this stage, but you need to make sure all relevant information, including types, are preserved.

A potential downside with such manual mapping is the tight coupling between the model and DTOs. One way to avoid this is to use the visitor pattern. This allows you to create a visitor to do the mapping, and to separate this from the actual model and/or DTOs. One possible benefit of this is that the compiler can ensure that each type is mapped, therefore reducing the risk that a mapping is forgotten when creating a new type.

It is also possible to implement JsonConverters for each of your types, where you have very fine grained control over how a type should be serialized/deserialized, and this may include mapping to another class and use that for serialization.

Using AutoMapper as you mention is another possible approach. I have no personal experience with this, so I cannot opine if it is suitable for your particular problem, or suggest how it should be used.

If you are unsure about the best approach I would encourage you to do some experimentation. Create some simplified test cases, test each approach and see what option you prefer.

Sign up to request clarification or add additional context in comments.

Comments

1

Something like this should do the trick:

var config = new MapperConfiguration(cfg => 
cfg.CreateMap<NotificationAlert, NotificationCreateRequestDTO>()
    .ForMember(dest => dest.DateTime, opt => opt.MapFrom(src => src.CreatedDate))
    .ForMember(dest => dest.Department, opt => opt.MapFrom(src => src.department.Name))
    .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src.GetType())));

var alertNotification = new NotificationAlert();
alertNotification.NotificationId = 1;
alertNotification.CreatedDate = DateTime.Now;
alertNotification.AlertMessage = "This is an alert message";
alertNotification.department = new Department() { Id = 101, Name = "HR" };

var mapper = new Mapper(config);
var dtoObject = mapper.Map<NotificationAlert, NotificationCreateRequestDTO>(alertNotification);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.