From 4e3b4146a3a5775f76f74559d1d145dc20a3b4b4 Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Thu, 28 May 2026 10:11:14 +0500 Subject: [PATCH 1/2] feat: add CustomResolvers support from Custom Source --- src/Mapster.Tests/WhenAddingCustomMappings.cs | 69 ++++++++++++++++++- .../Settings/ValueAccessingStrategy.cs | 2 +- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/Mapster.Tests/WhenAddingCustomMappings.cs b/src/Mapster.Tests/WhenAddingCustomMappings.cs index 51c44a0b..63683593 100644 --- a/src/Mapster.Tests/WhenAddingCustomMappings.cs +++ b/src/Mapster.Tests/WhenAddingCustomMappings.cs @@ -1,7 +1,9 @@ -using System; -using System.Collections.Generic; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Shouldly; +using System; +using System.Collections.Generic; +using System.Linq; +using static Mapster.Tests.WhenMappingDerived; namespace Mapster.Tests { @@ -44,8 +46,43 @@ public void Property_Is_Mapped_From_Null_Value_Successfully() dto.AnotherName.ShouldBeNull(); } + /// + /// https://github.com/MapsterMapper/Mapster/issues/980 + /// + [TestMethod] + public void ExtraSourceUsingCustomResolverSuccessfully() + { + TypeAdapterConfig config = new(); + config.NewConfig().Map(e => e, e => e.Props); + config.NewConfig, Dto980>() + .Map(e => e.Address, e => e.Get("Address")) + .Map(e => e.Description, e => e.Get("Description")) + .Map(e => e.Phone, e => e.Get("Phone")); + + Entity980 ent = new() + { + Name = "My entity", + Props = new(), + }; + + ent.Props.Add(new() { Key = "Phone", Value = "12345678" }); + ent.Props.Add(new() { Key = "Address", Value = "Default street" }); + ent.Props.Add(new() { Key = "Description", Value = "Sample text" }); + + + var dto = ent.Adapt(config); + + dto.Phone.ShouldBe("12345678"); + dto.Address.ShouldBe("Default street"); + dto.Description.ShouldBe("Sample text"); + } + #region TestClasses + + + + public class SimplePoco { public Guid Id { get; set; } @@ -98,4 +135,30 @@ public class CollectionDto #endregion } + class Entity980 + { + public string Name { get; set; } = default!; + public List Props { get; set; } = default!; + } + + public class EntityProp980 + { + public string Key { get; set; } = default!; + public string Value { get; set; } = default!; + } + + public class Dto980 + { + public string Name { get; set; } = default!; + public string? Address { get; set; } + public string? Description { get; set; } + public string? Phone { get; set; } + } + + public static class ListExtensions980 + { + // utility method as expression bodies are not allowed to have null propagating operator + public static string? Get(this List list, string key) + => list.FirstOrDefault(e => e.Key == key)?.Value; + } } \ No newline at end of file diff --git a/src/Mapster/Settings/ValueAccessingStrategy.cs b/src/Mapster/Settings/ValueAccessingStrategy.cs index f43f5ff3..4fb608dc 100644 --- a/src/Mapster/Settings/ValueAccessingStrategy.cs +++ b/src/Mapster/Settings/ValueAccessingStrategy.cs @@ -26,7 +26,7 @@ public static class ValueAccessingStrategy private static Expression? CustomResolverFn(Expression source, IMemberModel destinationMember, CompileArgument arg) { - var config = arg.Settings; + var config = source.Type == arg.SourceType ? arg.Settings : arg.Context.Config.GetMergedSettings(new TypeTuple(source.Type, arg.DestinationType),arg.MapType); var resolvers = config.Resolvers; if (resolvers.Count == 0) return null; From c5bda3d542120c733bbe3d7ae6db7c4bd9fbb7bc Mon Sep 17 00:00:00 2001 From: DocSvartz Date: Thu, 28 May 2026 14:28:11 +0500 Subject: [PATCH 2/2] feat(test): add revers type mapping test for #980 --- src/Mapster.Tests/WhenAddingCustomMappings.cs | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/Mapster.Tests/WhenAddingCustomMappings.cs b/src/Mapster.Tests/WhenAddingCustomMappings.cs index 63683593..d35fc55a 100644 --- a/src/Mapster.Tests/WhenAddingCustomMappings.cs +++ b/src/Mapster.Tests/WhenAddingCustomMappings.cs @@ -59,6 +59,12 @@ public void ExtraSourceUsingCustomResolverSuccessfully() .Map(e => e.Description, e => e.Get("Description")) .Map(e => e.Phone, e => e.Get("Phone")); + config + .NewConfig().Map(e => e.Props, e => e); + + config.NewConfig>() + .MapWith(x=> x.ToMemerList()); + Entity980 ent = new() { Name = "My entity", @@ -71,10 +77,20 @@ public void ExtraSourceUsingCustomResolverSuccessfully() var dto = ent.Adapt(config); - - dto.Phone.ShouldBe("12345678"); - dto.Address.ShouldBe("Default street"); - dto.Description.ShouldBe("Sample text"); + var entity = dto.Adapt(config); + + entity + .ShouldSatisfyAllConditions(() => + { + dto.Phone.ShouldBe("12345678"); + dto.Address.ShouldBe("Default street"); + dto.Description.ShouldBe("Sample text"); + + entity.Name.ShouldBe("My entity"); + entity.Props.Count.ShouldBe(3); + entity.Props[0].Key.ShouldBe("Phone"); + entity.Props[0].Value.ShouldBe("12345678"); + }); } #region TestClasses @@ -153,6 +169,17 @@ public class Dto980 public string? Address { get; set; } public string? Description { get; set; } public string? Phone { get; set; } + + public List ToMemerList() + { + var result = new List(); + + result.Add(new EntityProp980 { Key = "Phone", Value = Phone }); + result.Add(new() { Key = "Address", Value = Address }); + result.Add(new() { Key = "Description", Value = Description }); + + return result; + } } public static class ListExtensions980