Skip to content

src.Inner.Amount return Nullable<Money> when property Amount is not nullable #858

@DocSvartz

Description

@DocSvartz

.Map(dest => dest.InnerAmount, src => src.Inner.Amount)

I’ve encountered a similar issue.
Mapster correctly maps a direct property like: dest => dest.Amount, src => src.Amount.
However, it fails to map nested properties such as: dest => dest.InnerAmount, src => src.Inner.Amount

InnerAmount always ends up with the default value (NULL EUR).

Similar problem with PartnerCode.
bar.InnerPartnerCode.Value contains the type name instead of the expected value: "Tests.Tools.PartnerCode".

It works when I define mapping like this:

.Map(dest => dest.InnerPartnerCode, src => new PartnerCode(src.Inner.PartnerCode.Value, src.Inner.PartnerCode.IsForeigner))
.Map(dest => dest.InnerAmount, src => new Money(src.Inner.Amount.Amount, src.Inner.Amount.Currency))

I’m looking for a global solution, because I would prefer not to change all my individual mappings.
Has anyone experienced this or knows what might cause Mapster to behave differently for nested mappings?

[TestClass]
public class MapsterConverterTest
{
    [TestMethod]
    public async Task LoadTariffsTest()
    {
        // Arrange
        var config = TypeAdapterConfig.GlobalSettings;
        Register(config);
        config.Default.ShallowCopyForSameType(true);
        config.Default.MapToConstructor(true);
        config.Default.IgnoreNullValues(true);
        config.Default.MaxDepth(10);
        config.Default.NameMatchingStrategy(NameMatchingStrategy.Flexible);
        config.Default.AddDestinationTransform((string? s) => string.IsNullOrEmpty(s) ? null : s);
        config.RequireExplicitMapping = false;
        config.Compile();

        Foo foo = new()
        {
            Amount = new(1, Currency.Usd),
            PartnerCode = new("company", true),
            Inner = new()
            {
                Amount = new(10, Currency.Eur),
                Int = 100,
                PartnerCode = new("Inner company", true)
            }
        };

        // Act
        var bar = foo.Adapt<Bar>(config);

        // Assert
        Assert.AreEqual(10m, bar.InnerAmount.Amount);
    }

    private void Register(TypeAdapterConfig config)
    {
        config.ForType<Foo, Bar>()
            .Map(dest => dest.Amount, src => src.Amount)
            .Map(dest => dest.InnerAmount, src => src.Inner.Amount)
            .Map(dest => dest.PartnerCode, src => src.PartnerCode)
            .Map(dest => dest.InnerPartnerCode, src => src.Inner.PartnerCode)
            //.Map(dest => dest.InnerAmount, src => new Money(src.Inner.Amount, src.Inner.Amount.Currency))
            .Map(dest => dest.InnerInt, src => src.Inner.Int);

        config.NewConfig<PartnerCode, PartnerCode>()
            .MapWith(src => src);
    }
}

file class Foo
{
    public required Money Amount { get; set; }
    public PartnerCode PartnerCode { get; set; }
    public required FooInner Inner { get; set; }
}

file class FooInner
{
    public required Money Amount { get; set; }
    public PartnerCode PartnerCode { get; set; }
    public int Int { get; set; }
}

file class Bar
{
    public required Money Amount { get; set; }
    public required Money InnerAmount { get; set; }
    public int InnerInt { get; set; }
    public PartnerCode PartnerCode { get; set; }
    public PartnerCode InnerPartnerCode { get; set; }
}

public struct PartnerCode(string? value, bool isForeigner)
{
    public string? Value { get; set; } = value;
    public bool IsForeigner { get; set; } = isForeigner;
}

public struct Money
{
    public decimal? Amount { get; set; }

    public Currency Currency { get; set; } = Currency.Ron;

    public Money(decimal? amount, Currency currency = Currency.Eur)
    {
        Amount = amount;
        Currency = currency;
    }
}

Originally posted by @semi07 in #615

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions