diff --git a/src/Mapster.Tests/WhenMappingPrimitiveCustomMappingRegression.cs b/src/Mapster.Tests/WhenMappingPrimitiveCustomMappingRegression.cs new file mode 100644 index 00000000..d694ac67 --- /dev/null +++ b/src/Mapster.Tests/WhenMappingPrimitiveCustomMappingRegression.cs @@ -0,0 +1,147 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Mapster.Tests +{ + [TestClass] + public class WhenMappingPrimitiveCustomMappingRegression + { + [TestMethod] + public void CustomMappingDateTimeToPrimitive() + { + TypeAdapterConfig + .NewConfig() + .MapWith(src => new DateTimeOffset(src).ToUnixTimeSeconds()); + + TypeAdapterConfig + .NewConfig() + .MapWith(src => src.ToShortDateString()); + + var _source = new DateTime(2023, 10, 27, 0, 0, 0, DateTimeKind.Utc); + + var _resultToLong = _source.Adapt(); + var _resultToString = _source.Adapt(); + + _resultToLong.ShouldBe(new DateTimeOffset(new DateTime(2023, 10, 27, 0, 0, 0, DateTimeKind.Utc)).ToUnixTimeSeconds()); + _resultToString.ShouldNotBe(_source.ToString()); + _resultToString.ShouldBe(_source.ToShortDateString()); + } + + /// + /// https://github.com/MapsterMapper/Mapster/issues/561 + /// + [TestMethod] + public void MappingToPrimitiveInsiderWithCustomMapping() + { + TypeAdapterConfig, string?> + .NewConfig() + .MapToTargetWith((source, target) => source.HasValue ? source.Value : target); + + var sourceNull = new Source561 { Name = new Optional561(null) }; + var target = new Source561 { Name = new Optional561("John") }.Adapt(); + + var TargetDestinationFromNull = new Target561() { Name = "Me" }; + var NullToupdateoptional = sourceNull.Adapt(TargetDestinationFromNull); + var _result = sourceNull.Adapt(target); + + target.Name.ShouldBe("John"); + NullToupdateoptional.Name.ShouldBe("Me"); + } + + /// + /// https://github.com/MapsterMapper/Mapster/issues/407 + /// + [TestMethod] + public void MappingDatetimeToLongWithCustomMapping() + { + TypeAdapterConfig + .NewConfig() + .MapWith(src => new DateTimeOffset(src).ToUnixTimeSeconds()); + + TypeAdapterConfig + .NewConfig() + .MapWith(src => new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(src).Date); + + var emptySource = new Source407() { Time = DateTime.UtcNow.Date }; + var fromC1 = new DateTime(2023, 10, 27,0,0,0,DateTimeKind.Utc); + var fromC2 = new DateTimeOffset(new DateTime(2025, 11, 23, 0, 0, 0, DateTimeKind.Utc)).ToUnixTimeSeconds(); + var c1 = new Source407 { Time = fromC1 }; + var c2 = new Destination407 { Time = fromC2 }; + + var _result = c1.Adapt(); + var _resultLongtoDateTime = c2.Adapt(); + + _result.Time.ShouldBe(new DateTimeOffset(new DateTime(2023, 10, 27, 0, 0, 0, DateTimeKind.Utc)).ToUnixTimeSeconds()); + _resultLongtoDateTime.Time.ShouldBe(new DateTime(2025, 11, 23).Date); + } + + /// + /// https://github.com/MapsterMapper/Mapster/issues/407 + /// + [TestMethod] + public void CustomMappingPrimitiveToProjection() + { + TypeAdapterConfig + .NewConfig() + .MapWith(src => new DateTimeOffset(src).ToUnixTimeSeconds()); + + TypeAdapterConfig + .NewConfig() + .MapWith(src => new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(src).Date); + + var _sourceList = new List(); + _sourceList.Add(new Source407 { Time = new DateTime(2023, 10, 27, 0, 0, 0, DateTimeKind.Utc) }); + var _fromC2List = new List(); + _fromC2List.Add(new Destination407 { Time = new DateTimeOffset(new DateTime(2025, 11, 23, 0, 0, 0, DateTimeKind.Utc)).ToUnixTimeSeconds() }); + + var _resultProjectionDateTimeTolong = _sourceList.AsQueryable().ProjectToType().ToList(); + var _resultProjectionLongToDateTime = _fromC2List.AsQueryable().ProjectToType().ToList(); + + _resultProjectionDateTimeTolong[0].Time.ShouldBe(new DateTimeOffset(new DateTime(2023, 10, 27, 0, 0, 0, DateTimeKind.Utc)).ToUnixTimeSeconds()); + _resultProjectionLongToDateTime[0].Time.ShouldBe(new DateTime(2025, 11, 23).Date); + } + + } + + + #region TestClasses + + public class Source407 + { + public DateTime Time { get; set; } + } + + public class Destination407 + { + public long Time { get; set; } + } + + class Optional561 + { + public Optional561(T? value) + { + if (value != null) + HasValue = true; + + Value = value; + } + + public bool HasValue { get; } + public T? Value { get; } + } + + class Source561 + { + public Optional561 Name { get; set; } + } + + class Target561 + { + public string? Name { get; set; } + } + + #endregion TestClasses +} diff --git a/src/Mapster/Adapters/BaseAdapter.cs b/src/Mapster/Adapters/BaseAdapter.cs index b7b6fc67..92e9990a 100644 --- a/src/Mapster/Adapters/BaseAdapter.cs +++ b/src/Mapster/Adapters/BaseAdapter.cs @@ -85,6 +85,32 @@ protected virtual Expression CreateExpressionBody(Expression source, Expression? if (CheckExplicitMapping && arg.Context.Config.RequireExplicitMapping && !arg.ExplicitMapping) throw new InvalidOperationException("Implicit mapping is not allowed (check GlobalSettings.RequireExplicitMapping) and no configuration exists"); + #region CustomMappingPrimitiveImplimentation + + if (arg.Settings.MapToTargetPrimitive == true) + { + Expression dest; + + if (destination == null) + { + dest = arg.DestinationType.CreateDefault(); + } + else + dest = destination; + + var customConvert = arg.Context.Config.CreateMapToTargetInvokeExpressionBody(source.Type, arg.DestinationType, source, dest); + + arg.MapType = MapType.MapToTarget; + return customConvert; + } + + if (arg.Settings.MapWithToPrimitive == true) + { + return arg.Context.Config.CreateMapInvokeExpressionBody(source.Type, arg.DestinationType, source); + } + + #endregion CustomMappingPrimitiveImplimentation + var oldMaxDepth = arg.Context.MaxDepth; var oldDepth = arg.Context.Depth; try diff --git a/src/Mapster/Adapters/PrimitiveAdapter.cs b/src/Mapster/Adapters/PrimitiveAdapter.cs index 2a407a89..cfc7c809 100644 --- a/src/Mapster/Adapters/PrimitiveAdapter.cs +++ b/src/Mapster/Adapters/PrimitiveAdapter.cs @@ -18,6 +18,24 @@ protected override bool CanMap(PreCompileArgument arg) protected override Expression CreateExpressionBody(Expression source, Expression? destination, CompileArgument arg) { + + if (arg.Settings.MapToTargetPrimitive == true) + { + Expression dest; + + if (destination == null) + { + dest = arg.DestinationType.CreateDefault(); + } + else + dest = destination; + + var customConvert = arg.Context.Config.CreateMapToTargetInvokeExpressionBody(source.Type, arg.DestinationType, source, dest); + + arg.MapType = MapType.MapToTarget; + return customConvert; + } + Expression convert = source; var sourceType = arg.SourceType; var destinationType = arg.DestinationType; diff --git a/src/Mapster/TypeAdapterSetter.cs b/src/Mapster/TypeAdapterSetter.cs index 9215f375..c0bf07c4 100644 --- a/src/Mapster/TypeAdapterSetter.cs +++ b/src/Mapster/TypeAdapterSetter.cs @@ -611,6 +611,11 @@ public TypeAdapterSetter MapWith(Expression MapToTargetWith(Expression Set(nameof(GenerateMapper), value); } + public bool? MapWithToPrimitive + { + get => Get(nameof(MapWithToPrimitive)); + set => Set(nameof(MapWithToPrimitive), value); + } + + /// + /// Not implemented + /// + public bool? MapToPrimitive + { + get => Get(nameof(MapToPrimitive)); + set => Set(nameof(MapToPrimitive), value); + } + + public bool? MapToTargetPrimitive + { + get => Get(nameof(MapToTargetPrimitive)); + set => Set(nameof(MapToTargetPrimitive), value); + } + public List> ShouldMapMember { get => Get(nameof(ShouldMapMember), () => new List>()); diff --git a/src/Mapster/Utils/ReflectionUtils.cs b/src/Mapster/Utils/ReflectionUtils.cs index fe44e790..1de6e7ae 100644 --- a/src/Mapster/Utils/ReflectionUtils.cs +++ b/src/Mapster/Utils/ReflectionUtils.cs @@ -36,6 +36,11 @@ public static Type GetTypeInfo(this Type type) } #endif + public static bool IsMapsterPrimitive(this Type type) + { + return _primitiveTypes.TryGetValue(type, out var primitiveType) || type == typeof(string); + } + public static bool IsNullable(this Type type) { return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);