Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support Custom mapping from Mapping To Primitive #650

Open
wants to merge 8 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
147 changes: 147 additions & 0 deletions src/Mapster.Tests/WhenMappingPrimitiveCustomMappingRegression.cs
Original file line number Diff line number Diff line change
@@ -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<DateTime, long>
.NewConfig()
.MapWith(src => new DateTimeOffset(src).ToUnixTimeSeconds());

TypeAdapterConfig<DateTime, string>
.NewConfig()
.MapWith(src => src.ToShortDateString());

var _source = new DateTime(2023, 10, 27, 0, 0, 0, DateTimeKind.Utc);

var _resultToLong = _source.Adapt<long>();
var _resultToString = _source.Adapt<string>();

_resultToLong.ShouldBe(new DateTimeOffset(new DateTime(2023, 10, 27, 0, 0, 0, DateTimeKind.Utc)).ToUnixTimeSeconds());
_resultToString.ShouldNotBe(_source.ToString());
_resultToString.ShouldBe(_source.ToShortDateString());
}

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/561
/// </summary>
[TestMethod]
public void MappingToPrimitiveInsiderWithCustomMapping()
{
TypeAdapterConfig<Optional561<string?>, string?>
.NewConfig()
.MapToTargetWith((source, target) => source.HasValue ? source.Value : target);

var sourceNull = new Source561 { Name = new Optional561<string?>(null) };
var target = new Source561 { Name = new Optional561<string>("John") }.Adapt<Target561>();

var TargetDestinationFromNull = new Target561() { Name = "Me" };
var NullToupdateoptional = sourceNull.Adapt(TargetDestinationFromNull);
var _result = sourceNull.Adapt(target);

target.Name.ShouldBe("John");
NullToupdateoptional.Name.ShouldBe("Me");
}

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/407
/// </summary>
[TestMethod]
public void MappingDatetimeToLongWithCustomMapping()
{
TypeAdapterConfig<DateTime, long>
.NewConfig()
.MapWith(src => new DateTimeOffset(src).ToUnixTimeSeconds());

TypeAdapterConfig<long, DateTime>
.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<Destination407>();
var _resultLongtoDateTime = c2.Adapt<Source407>();

_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);
}

/// <summary>
/// https://github.com/MapsterMapper/Mapster/issues/407
/// </summary>
[TestMethod]
public void CustomMappingPrimitiveToProjection()
{
TypeAdapterConfig<DateTime, long>
.NewConfig()
.MapWith(src => new DateTimeOffset(src).ToUnixTimeSeconds());

TypeAdapterConfig<long, DateTime>
.NewConfig()
.MapWith(src => new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(src).Date);

var _sourceList = new List<Source407>();
_sourceList.Add(new Source407 { Time = new DateTime(2023, 10, 27, 0, 0, 0, DateTimeKind.Utc) });
var _fromC2List = new List<Destination407>();
_fromC2List.Add(new Destination407 { Time = new DateTimeOffset(new DateTime(2025, 11, 23, 0, 0, 0, DateTimeKind.Utc)).ToUnixTimeSeconds() });

var _resultProjectionDateTimeTolong = _sourceList.AsQueryable().ProjectToType<Destination407>().ToList();
var _resultProjectionLongToDateTime = _fromC2List.AsQueryable().ProjectToType<Source407>().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<T>
{
public Optional561(T? value)
{
if (value != null)
HasValue = true;

Value = value;
}

public bool HasValue { get; }
public T? Value { get; }
}

class Source561
{
public Optional561<string?> Name { get; set; }
}

class Target561
{
public string? Name { get; set; }
}

#endregion TestClasses
}
26 changes: 26 additions & 0 deletions src/Mapster/Adapters/BaseAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions src/Mapster/Adapters/PrimitiveAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions src/Mapster/TypeAdapterSetter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,11 @@ internal TypeAdapterSetter(TypeAdapterSettings settings, TypeAdapterConfig paren
{
this.CheckCompiled();

if(typeof(TSource).IsMapsterPrimitive() || typeof(TDestination).IsMapsterPrimitive())
{
this.Settings.MapWithToPrimitive = true;
}

if (applySettings)
{
var adapter = new DelegateAdapter(converterFactory);
Expand All @@ -634,6 +639,11 @@ internal TypeAdapterSetter(TypeAdapterSettings settings, TypeAdapterConfig paren
{
this.CheckCompiled();

if (typeof(TSource).IsMapsterPrimitive() || typeof(TDestination).IsMapsterPrimitive())
{
this.Settings.MapToTargetPrimitive = true;
}

if (applySettings)
{
var adapter = new DelegateAdapter(converterFactory);
Expand Down
21 changes: 21 additions & 0 deletions src/Mapster/TypeAdapterSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,27 @@ public NameMatchingStrategy NameMatchingStrategy
set => Set(nameof(GenerateMapper), value);
}

public bool? MapWithToPrimitive
{
get => Get(nameof(MapWithToPrimitive));
set => Set(nameof(MapWithToPrimitive), value);
}

/// <summary>
/// Not implemented
/// </summary>
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<Func<IMemberModel, MemberSide, bool?>> ShouldMapMember
{
get => Get(nameof(ShouldMapMember), () => new List<Func<IMemberModel, MemberSide, bool?>>());
Expand Down
5 changes: 5 additions & 0 deletions src/Mapster/Utils/ReflectionUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<>);
Expand Down