Mapster icon indicating copy to clipboard operation
Mapster copied to clipboard

Mapping Optional<T> to T

Open furier opened this issue 1 year ago • 8 comments

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.

furier avatar Mar 09 '23 16:03 furier

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 !

larsbloch avatar Mar 11 '23 21:03 larsbloch

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 avatar Mar 13 '23 10:03 furier

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>()

furier avatar Mar 14 '23 15:03 furier

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

DocSvartz avatar Sep 30 '23 11:09 DocSvartz

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 avatar Oct 03 '23 07:10 DocSvartz

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 avatar Oct 04 '23 01:10 DocSvartz

Hello, probably it you want IgnorIF

DocSvartz avatar Oct 04 '23 06:10 DocSvartz

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


DocSvartz avatar Oct 24 '23 11:10 DocSvartz