MapsterMapper / Mapster

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

Mapping Optional<T> to T #561

Open furier opened 1 year ago

furier commented 1 year ago

I have a situation where I only want the Source.Name property to be mapped to the Target.Name property if the HasValue flag of the Source.Name property is true.

struct Optional<T> {
    public bool HasValue { get; }
    public T? Value { get; }
}

class Source { 
    public Optional<string?> Name { get; set; }
}

class Target { 
    public string Name { get; set; }
}

I've tried to create a custom mapping using the TypeAdapterConfig object as shown below, but the Target.Name property never gets a value.

public void Register(TypeAdapterConfig config)
{
    config
        .NewConfig<Optional<string?>, string>()
        .MapToTargetWith((source, target) => source.HasValue ? source.Value : target);
}

Here's the code I'm using to adapt the source object to a target object:

var target = new Source { Name = new Optional<string?>("John") }.Adapt<Target>();

I have so incredibly many source and target types that I dont want to explicitly create manual mapping for all the types, but in stead tell Mapster that when it encounters a property on a source class with the type of Optional<T>, only map it if the HasValue flag is true.

I am doing this a part of my HotChocolate GraphQL API, here is a link to the documentation regarding Optional properties.

larsbloch commented 1 year ago

I have asked a similar question on hotchocolates slack channel the other day. I have yet to receive something usefull. If we can figure this out i will share it with the rest of the project. I think many people could take advantage of this usecase !

furier commented 1 year ago

With AutoMapper you can do the following to add a type conversion, and it works great. However I was hoping to use Mapster as it is significantly faster.

config
    .CreateMap<Optional<T>, T>()
    .ConvertUsing((s, d) => s.HasValue ? s.Value : d)
furier commented 1 year ago

The problem seems to be related to Nullable<T> as it works perfectly fine when none of the source or target T is Nullable<T> eg: .NewConfig<Optional<string>, string>()

DocSvartz commented 11 months ago

Hello, String is the same Class, what member do you want to replace with a string or this just stated as an example?

DocSvartz commented 11 months ago

Sorry, I misunderstood the question. I thought you were looking for a solution to the fact that the update TDistination has null When null was not transmitted TSource.

DocSvartz commented 11 months ago

Hello, problem in this. In this example, the conversion to a primitive. The primitive adapter does not support custom mapping processing. In this sample in to destination set value from method .ToString() Source object, not from source.Value. MapToTargetWith() doesn't work.

You @furier were right if Tsource == null && map != Projection working this section and return default(TDistination)

If this not work from not primitive class please add a sample.

DocSvartz commented 11 months ago

Hello, probably it you want IgnorIF

DocSvartz commented 10 months ago

Hello @furier, The underlying problem here is that .MapToTargetWith() only works when you are updating existing instances of your objects:

_Optional<string> _Optional = new();
Target _target = new();
_Optional.adapt(_target);

in this case .MapToTargetWith() not work:

var target = new Source { Name = new Optional<string?>("John") }.Adapt<Target>();

I get behavior you want from primitive type, you wanted to get this for all types ?

[TestMethod]
public void OptionalT()
{

    TypeAdapterConfig<Optional561<string>, string>
        .NewConfig()
        .MapToTargetWith((source, target) => source.HasValue ? source.Value : target)
        .MapToTargetPrimitive(true) // wip func
        .IgnoreNullValues(true);

    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 NullOptionalUpdateTarget= sourceNull.Adapt(TargetDestinationFromNull); //   Target.Name = "ME"

    var _result = sourceNull.Adapt(target);
    target.Name.ShouldBe("John");

}

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