MapsterMapper / Mapster

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

Cannot map properties that have differt names and init access modifier #656

Open nicoarm93 opened 8 months ago

nicoarm93 commented 8 months ago

Hi, I have a problem with version 7.4. If I want to map two properties with different names and the destination has a different name and the init modifier, Mapster throws a NullReferenceException. This happens when in the configuration of Mapster I set PreserveReference to true.

Here's the code

{
    public Mapper Mapper { get; }

    public MapsterTest()
    {
        var config = new TypeAdapterConfig();
        config.Default.Settings.PreserveReference = true;
        config.Scan(Assembly.GetAssembly(typeof(MapsterTest)));
        config.Compile();
        Mapper = new Mapper(config);
    }
}

internal class MyClass
{
    public int MyProperty { get; set; }
}

internal class DtoClass
{
    public int MyPropertyDto { get; init; }
}
internal class Register : IRegister
{
    void IRegister.Register(TypeAdapterConfig config)
    {
        config.NewConfig<MyClass, DtoClass>()
            .Map(x => x.MyPropertyDto, x=> x.MyProperty);
    }
}

And here's the mapping


var from = new MyClass { MyProperty = 1};

var to = mapster.Mapper.Map<DtoClass>(from);

(The problem does not manifest if the properties have the same name or another modifier (set; or private set;) This code works fine with version 7.3

DocSvartz commented 7 months ago

linked #422

@nicoarm93 @andrerav location of this bug found )

PR with fix is open #658

Mat3oo commented 7 months ago

In my case similar regression between 7.4.0 and 7.3.0 (Mapster throws a NullReferenceException) is not related to PreserveReference. It's about init only properties, when mapping to target object. Below code works fine with Mapster 7.3.0, but doesn't work with Mapster 7.4.0.

using Mapster;

TypeAdapterConfig<Core.RootSource.NestedSource, Core.RootDestination.NestedDestination>
    .NewConfig()
    .Map(dest => dest.NameDest, src => src.NameSrc);

var dest = new Core.RootDestination { MyProperty = null };

var mapped = Core.RootSource.Create().Adapt(dest);

Console.WriteLine(mapped);

namespace Core
{
    public class RootSource
    {
        public required NestedSource? MyProperty { get; set; }

        public static RootSource Create()
        {
            return new RootSource
            {
                MyProperty = new NestedSource
                {
                    NameSrc = "NotEmptyString"
                }
            };
        }

        public class NestedSource
        {
            public required string NameSrc { get; set; }
        }
    }

    public class RootDestination
    {
        public NestedDestination? MyProperty { get; set; }

        public override string ToString()
        {
            return $"{nameof(RootDestination)}.{nameof(MyProperty)}={MyProperty?.NameDest}";
        }

        public class NestedDestination
        {
            public string? NameDest { get; init; }
        }
    }
}
DocSvartz commented 7 months ago

Hello @Mat3oo, @andrerav I checked my fix. The bug indicated by @Mat3oo persists. The place of origin itself is most likely identified correctly. But the created working code and the created non-working code, at first glance, do not make sense and both should not work))

Working Code :

.Lambda #Lambda1<System.Func`3[Core.RootSource+NestedSource,Core.RootDestination+NestedDestination,Core.RootDestination+NestedDestination]>(
    Core.RootSource+NestedSource $var1,
    Core.RootDestination+NestedDestination $var2) {
    .Block(Core.RootDestination+NestedDestination $result) {
        .If ($var1 == null) {
            .Return #Label1 { null }
        } .Else {
            .Default(System.Void)
        };
        $result = ($var2 ?? .New Core.RootDestination+NestedDestination());
        .Block() {
            $result.NameDest = $var1.NameSrc
        };
        .Return #Label1 { $result };
        .Label
            null
        .LabelTarget #Label1:
    }
}

Non-working code (throws a NullReferenceException):

.Lambda #Lambda1<System.Func`3[Core.RootSource+NestedSource,Core.RootDestination+NestedDestination,Core.RootDestination+NestedDestination]>(
    Core.RootSource+NestedSource $var1,
    Core.RootDestination+NestedDestination $var2) {
    .Block(Core.RootDestination+NestedDestination $result) {
        .If ($var1 == null) {
            .Return #Label1 { null }
        } .Else {
            .Default(System.Void)
        };
        $result = ($var2 ?? .New Core.RootDestination+NestedDestination());
        .Block() {
            .Call (.Call .Constant<System.RuntimeType>(Core.RootDestination+NestedDestination).GetProperty("NameSrc")).SetValue(
                $result,
                $var1.NameSrc)
        };
        .Return #Label1 { $result };
        .Label
            null
        .LabelTarget #Label1:
    }
}