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 for mapping base type when using MapDerivedType #692

Open
simo026q opened this issue Aug 28, 2023 · 9 comments
Open

Add support for mapping base type when using MapDerivedType #692

simo026q opened this issue Aug 28, 2023 · 9 comments
Labels
enhancement New feature or request

Comments

@simo026q
Copy link

Is your feature request related to a problem? Please describe.
I want to create a mapper that can map to a derived type and the base type. I could not find a way to do this in the documentation.

I tried doing this but it creates a infinite loop:

[MapDerivedType<Derived, DerivedDto>]
[MapDerivedType<Base, BaseDto>]
public partial BaseDto Map(Base source);

Describe the solution you'd like
I would like the option to default to the base type, map to the base type if it is the exact type of the base type and not derived from it, and then the option to throw an exception as it is right now.

Something like this enum:

public enum DerivedTypeMappingStrategy
{
    OnlyDerived, // Current implementation
    DerivedOrBase, // Map to specified derived type or base
    DefaultToBase // Default to base if no specified derived type is found
}

It could be set by a new attribute that should be added.

@latonz
Copy link
Contributor

latonz commented Aug 28, 2023

I don't fully get what you are requested... Could you provide an example how the configuration and how the generated could would look like to address your needs?

@simo026q
Copy link
Author

I don't fully get what you are requested... Could you provide an example how the configuration and how the generated could would look like to address your needs?

Sure :)

Example:

[Mapper]
public partial class Mapper
{
    [MapDerivedType<DerivedDto, Derived>]
    public partial Base Map(BaseDto source);
}

public class Base
{
    public int Number { get; set; }
}

public class BaseDto
{
    public int Number { get; set; }
}

public class Derived : Base
{
    public string? Name { get; set; }
}

public class DerivedDto : BaseDto
{
    public string? Name { get; set; }
}

This is the generated code:

public partial global::Base Map(global::BaseDto source)
    {
        return source switch
        {
            global::DerivedDto x => MapToDerived(x),
            _ => throw new System.ArgumentException($"Cannot map {source.GetType()} to Base as there is no known derived type mapping", nameof(source)),
        };
    }

I want a option to generate this code:

public partial global::Base Map(global::BaseDto source)
    {
        return source switch
        {
            global::DerivedDto x => MapToDerived(x),
            _ => MapToBase(source)
        };
    }

So it just maps it to the base class if instead of throwing an exception. And there could also be an option that, instead of always defaulting to the base class, it will throw an exception if typeof(BaseDto).IsSubclassOf(source.GetType()) is true (so that it can only be a specified derived type or the exact type of the base type).
I don't know if it makes sence.

@latonz latonz added the enhancement New feature or request label Aug 28, 2023
@latonz
Copy link
Contributor

latonz commented Aug 28, 2023

Ah got it 😊
I think we should make this configurable.

Proposed API

public sealed class MapperAttribute : Attribute
{
+   public DerivedTypeMappingFallbackStrategy DerivedTypeMappingsFallback { get; set; } = DerivedTypeFallbackStrategy.Throw;
}

+[AttributeUsage(AttributeTargets.Method)]
+public sealed class MapDerivedTypes : Attriubte
+{
+     public DerivedTypeMappingFallbackStrategy FallbackStrategy { get; set; } = DerivedTypeFallbackStrategy.Throw;
+}

+public enum DerivedTypeMappingFallbackStrategy
+{
+    Throw,
+    MapBaseType,
+}

Feel free to comment on the proposed API.
Happy to accept a PR, let me know if you need any hints 😊

@simo026q
Copy link
Author

Maybe there could also be an option to generate something like this:

public partial global::Base Map(global::BaseDto source)
    {
        return source switch
        {
            global::DerivedDto x => MapToDerived(x),
            global::BaseDto x when !typeof(BaseDto).IsSubclassOf(source.GetType()) => MapToBase(x),
            _ => throw new System.ArgumentException($"Cannot map {source.GetType()} to Base as there is no known derived type mapping", nameof(source))
        };
    }

And then maybe a enum like this:

public enum DerivedTypeMappingStrategy
{
    ExcludeBaseType,
    IncludeBaseType,
    FallbackToBaseType
}

However, it could also just be an attribute:

[AttributeUsage(AttributeTargets.Method)]
public class MapBaseTypeAttribute : Attribute
{
    public bool FallbackToBaseType { get; set; } = false;
}

@latonz
Copy link
Contributor

latonz commented Aug 30, 2023

So global::BaseDto x when !typeof(BaseDto).IsSubclassOf(source.GetType()) should map only subclasses of BaseDto but not BaseDto itself? Whats the use case for this?

@simo026q
Copy link
Author

simo026q commented Aug 30, 2023

So global::BaseDto x when !typeof(BaseDto).IsSubclassOf(source.GetType()) should map only subclasses of BaseDto but not BaseDto itself? Whats the use case for this?

No the exact opposite. Of course the use case for this is still pretty small but just defaulting to the base type might lead to some unexpected behaviours.
This is definitely not a need to have, but imo it would be a great addition.

Edit: An example of unexpected behaviour: you might not want a derived to be mapped or you might expect it to map all the derived types properties if you are not fully familiar with all of the code base (e.g. when collaborating on larger projects). With the global::BaseDto x when !typeof(BaseDto).IsSubclassOf(source.GetType()) you know exactly what is going to happen if the type is not explicitly specified.

@latonz
Copy link
Contributor

latonz commented Aug 31, 2023

@simo026q wouldn't then a check in the form global::BaseDto x when typeof(BaseDto) == source.GetType() be sufficient?

@simo026q
Copy link
Author

@simo026q wouldn't then a check in the form global::BaseDto x when typeof(BaseDto) == source.GetType() be sufficient?

Yeah true, that would do the same thing

@latonz
Copy link
Contributor

latonz commented Aug 31, 2023

I can't think of a use-case for which this is useful. Can you elaborate?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants