MapsterMapper / Mapster

A fast, fun and stimulating object to object Mapper
MIT License
4.32k stars 329 forks source link

Map to derived type #663

Closed benjaminoerskov closed 9 months ago

benjaminoerskov commented 10 months ago

Hello Could you please help me how I can map to a derived type, without knowing the sourcetype on compiletime? Here are my classes

public abstract class Answer
{
    public bool Completed { get; set; }
}
public class ImageAnswer : Answer
{   
    // todo
}
public abstract class AnswerDto
{
    public bool Completed { get; set; }
}
public class ImageAnswerDto : AnswerDto
{
    // todo
}

My config:

        TypeAdapterConfig<AnswerDto, Answer>.NewConfig()
            .Include<ImageAnswerDto, ImageAnswer>();

Now when I map ImageAnswerDto to Answer, I would expect it to map to ImageAnswer, but instead it throws:

System.InvalidOperationException
Cannot instantiate type: Answer

My mapping:

    public async Task Create(AnswerDto dto) // AnswerDto type is ImageAnswerDto
    {
        var answer = dto.Adapt<Answer>();
    }
luczito commented 10 months ago

I have a similar issue https://github.com/MapsterMapper/Mapster/issues/664

You can achieve the mapping by defining custom mapping logic for each mapping pair as:

TypeAdapterConfig<AnswerDto, Answer>.NewConfig()
            .Include<ImageAnswerDto, ImageAnswer>();
TypeAdapterConfig<ImageAnswerDto, Answer>.NewConfig()
           .ConstructUsing(src => new ImageAnswer{Completed = src.Completed});

But this does require a lot of custom configs if your service / application are large with many derived classes since you have to define a config for each pair.

benjaminoerskov commented 10 months ago

@luczito Yea but then I have to update this mapping every time I add more properties. Thats the whole point of using a mapper framework, that all of these mappings is handled automatically. I feel like this is no better than writing my own mapperfunctions, and getting rid of Mapster alltogether?

luczito commented 10 months ago

@benjaminoerskov I totally agree hence my own issue on the topic as well. I know Automapper can do this mapping automatically so it confuses me that it seems it isn't a feature with Mapster yet.

benjaminoerskov commented 10 months ago

I found a workaround. If i map to the dto's and the entity's parent class, it maps it correctly:

public static Answer ToEntity(this AnswerDto dto)
{
    var stepStateDto = new StepStateDTO
    {
        Answer = dto
    };

    var stepState = stepStateDto.Adapt<StepState>();

    return stepState.Answer;
}

public static AnswerDto ToDto(this Answer answer)
{
    var stepState = new StepState
    {
        Answer = answer
    };

    var stepStateDto = stepState.Adapt<StepStateDTO>();

    return stepStateDto.Answer;
}

My parentmodels:

public class StepStateDTO
{
    public AnswerDto Answer { get; set; }
}
public class StepState
{
    public Answer Answer { get; set; }
}
luczito commented 9 months ago

Old threat but i'll update with my finding:

You can solve the issue without declaring a stepstate class:

Define your mapping config as the base class with derived class mapping included:

   config.NewConfig<Answer, AnswerDto>()
      .Include<ImageAnswer, ImageAnswerDto>();

When mapping directly to base class from derived class include both source base and target base as arguments:

var answer = dto.Adapt<AnswerDto, Answer>();

This will result in the correct mapping to ImageAnswer. This is mentioned in the Include derived types block of the wiki: https://github.com/MapsterMapper/Mapster/wiki/Config-inheritance

benjaminoerskov commented 9 months ago

@luczito Thank you! I must have missed that part of the docs.