Mapster icon indicating copy to clipboard operation
Mapster copied to clipboard

Interface Property not mapping

Open aicukltd opened this issue 3 years ago • 4 comments

Hello

I love the lightweight powerful mapper however I am struggling with one element of the mapping capability.

Class public class TypeA { ITypeB TypeB {get;set;} }

Mapping var destination = source.Adapt<TSource, TDestination>();

Result public class TypeA { GeneratedType_1 TypeB {get;set;} }

  1. I have a class that implements ITypeB.

Any help would be appreciated.

aicukltd avatar Nov 24 '21 23:11 aicukltd

Further to this, this is the class that registers my configs.

Method to Register

public static void RegisterMapster(IServiceCollection serviceCollection) {

    var externalModelsAssemblyTypes = Assembly.Load("Project.Models.Persistence.External").DetectTypesToRegisterAndMatchToInterface();

    var externalSharpModelsAssemblyTypes = Assembly.Load("ExternalSharp").DetectTypesToRegisterAndMatchOnName(externalModelsAssemblyTypes.Keys);

    foreach (var externalModelsAssemblyType in externalModelsAssemblyTypes)
    {
        TypeAdapterConfig.GlobalSettings.ForType(externalModelsAssemblyType.Key, externalModelsAssemblyType.Value).PreserveReference(true);
        TypeAdapterConfig.GlobalSettings.ForType(externalModelsAssemblyType.Value, externalModelsAssemblyType.Key).PreserveReference(true);
    }

    foreach (var externalSharpModelsAssemblyType in externalSharpModelsAssemblyTypes)
    {
        TypeAdapterConfig.GlobalSettings.ForType(externalSharpModelsAssemblyType.Value, externalSharpModelsAssemblyType.Key).PreserveReference(true);
        TypeAdapterConfig.GlobalSettings.ForType(externalSharpModelsAssemblyType.Key, externalSharpModelsAssemblyType.Value).PreserveReference(true);
    }

    TypeAdapterConfig<ExternalSharp.Customer, Customer>.NewConfig().Map(x => x.ExternalObjectId, x => x.Id).Ignore(destinationMember => destinationMember.Id).TwoWays();
    TypeAdapterConfig<ExternalSharp.Order, Order>.NewConfig().Map(x => x.ExternalObjectId, x => x.Id).Ignore(destinationMember => destinationMember.Id).TwoWays();
    TypeAdapterConfig<ExternalSharp.Product, Product>.NewConfig().Map(x => x.ExternalObjectId, x => x.Id).Ignore(destinationMember => destinationMember.Id).TwoWays();
    TypeAdapterConfig<ExternalSharp.Address, IAddress>.NewConfig().Map(x => x.ExternalObjectId, x => x.Id).TwoWays();
}

aicukltd avatar Nov 25 '21 07:11 aicukltd

@aicukltd It seems your example uses dynamic assembly loading, and so it is difficult to tell if the problem you are seeing is related to that or to a potential bug in Mapster. Can you try making a simple reproducible example without dynamic assembly loading?

andrerav avatar Feb 20 '22 12:02 andrerav

The problem is when target propety is an interface. Mapster always generates some proxy type for such a property, even when the value implements this interface.

In the following test case, the first assertion is OK, InterfaceProp.Value has correct value. But the second assertion fails, because the value of this property is not ValueData type, but some generated proxy type for interface IValueData.

If the target property is of interface type and source value implements this interface, this value should be just assigned to the property without doing anything else. The problem is more obvious, if the value is not just a simple POCO, but a class with some business logic (methods). Now all such a methods throws NotImplementedException.

[TestMethod]
public void MapToInterface()
{
    TypeAdapterConfig<SourceDto, TargetDto>.NewConfig()
        .Map(dest => dest.InterfaceProp, src => new ValueData { Value = src.DataProp });

    SourceDto source = new SourceDto { DataProp = "Dolor" };
    TargetDto target = source.Adapt<TargetDto>();

    target.ShouldNotBeNull();
    target.ShouldSatisfyAllConditions(
        () => target.InterfaceProp.Value.ShouldBe("Dolor"),
        () => target.InterfaceProp.ShouldBeOfType<ValueData>()
    );

    // This throws NotImplementedException.
    target.InterfaceProp.DoWork();
}

public interface IValueData
{
    string Value { get; set; }
    void DoWork();
}

public class ValueData : IValueData
{
    public string Value { get; set; }
    public void DoWork() { }
}

public class TargetDto
{
    public IValueData InterfaceProp { get; set; }
}

public class SourceDto
{
    public string DataProp { get; set; }
}

satano avatar May 24 '22 10:05 satano

Well I managed to make this work. I need to specify explicit mapping configuration between that target interface type and used source type:

TypeAdapterConfig<string, IValueData>.NewConfig()
    .MapWith(src => new ValueData { Value = src });

But it would be nice to have MapWith method for specific destination properties, not for all properties of some type. Something like:

TypeAdapterConfig<SourceDto, TargetDto>.NewConfig()
    .MapWith(dest => dest.InterfaceProp, src => OurCustomLogicToCreateInstance(src));

satano avatar May 24 '22 14:05 satano

@satano Sorry for the late reply. Unfortunately we can't support this scenario due to code generation limitations. Glad you figured it out :)

andrerav avatar May 29 '23 21:05 andrerav

you can also ignore mapping that property and use AfterMapping to overcome this problem. TypeAdapterConfig<SourceDto, TargetDto>.NewConfig().Ignore(dest => dest.InterfaceProp).AfterMapping((src, dest) => dest.InterfaceProp, src => new ValueData { Value = src.DataProp });

mehrdades avatar Jun 20 '23 17:06 mehrdades