Skip to content

Mapping nullable Enum to custom object #640

Closed

Description

@WahidBitar

for now I can have something like the following to register mapping for all enums in the system

config.NewConfig<Enum, KeyValueData>()
            .MapWith(s => new KeyValueData(s, Enums.ResourceManager));

However, it's not possible to have the same cofig for a nullable Enum.

The following config is not working:

config.NewConfig<Enum?, KeyValueData?>()
            .MapWith(s => s == null ? null : new KeyValueData(s.ToString(), Enums.ResourceManager));

I'll get the following Exception

Error while compiling
source=Tests.FirstClass
destination=Tests.SecondClass
type=Map
-> InnerException:
Error while compiling
source=System.Nullable`1[Model.IdentityType]
destination=Model.KeyValueData
type=Map
--> InnerException:
No default constructor for type 'KeyValueData', please use 'ConstructUsing' or 'MapWith'

Activity

  1. WahidBitar commented on Oct 8, 2023

    @WahidBitar
    Author

    I've figured a way out but still think it might be a better if we've somthing more helpful in the config. Especially from primitive types to classes. Anyways, my solution was simply like the following:

    config.NewConfig(typeof(Nullable<>), typeof(KeyValueData))
                .IgnoreNullValues(true)
                .Map("key", "Value")
                .MapToConstructor(true);
    
  2. DocSvartz commented on Oct 8, 2023

    @DocSvartz
    Contributor

    Hello @WahidBitar , if your goal is to skipped mapping when the TSource is null, maybe your help .IgnorIf()

  3. DocSvartz commented on Oct 9, 2023

    @DocSvartz
    Contributor

    Hello @WahidBitar, I ran into this problem while preparing a fix for the problem record detection.

    Please add sample Type of KeyValueData. To make it easier to understand which way the processing is going

  4. WahidBitar commented on Oct 9, 2023

    @WahidBitar
    Author

    Thanks @DocSvartz , My main goal is not to skip the null values. My goal is to map from a nullable enum to my custom class KeyValueData in a general way.
    I'll put here the entire aspects and my work around so you may take benifit of it.

    Note: you may found something weard in my KeyValueData class because I have a general mapper from Enum to string because I put translation of the enums in the Resources files, so If you don't have this case your config should be simpler. Anyway here is my classes and config:

    Mapping config:

    public class GeneralMapper : IRegister
    {
        public void Register(TypeAdapterConfig config)
        {
            // by default the following config will cause the Key property in the KeyValueData to get the value from the resources.
            // so I've updated the KeyValueData class to ignore the setter
            config.ForType<Enum, string?>()
                .MapWith(e => Enums.ResourceManager.GetString(e.ToResourceKey()) ?? e.ToString());
            
            config.NewConfig<Enum, KeyValueData?>()
                .MapWith(s => new KeyValueData(s));
            
            config.NewConfig(typeof(Nullable<>), typeof(KeyValueData))
                .MapToConstructor(true)
                .Map("key", "Value");
        }
    }
    

    the KeyValueData class:

    public record KeyValueData
    {
        private readonly string? keyHolder; // I had to set this field to this name becuase key will get the value from Resources
        private string? description;
    
        public KeyValueData(Enum key)
        {
            this.keyHolder = key.ToString();
            Description = Enums.ResourceManager.GetString(key.ToResourceKey()) ?? key.ToResourceKey();
        }
    
        public string Key
        {
            get => keyHolder!;
            set { } // Don't know why mapster call the setter after calling the constructor. I guess there is a bug related to this.
        }
    
        public string? Description
        {
            get => description;
            set => description ??= value;
        }
    }
    
  5. DocSvartz commented on Oct 9, 2023

    @DocSvartz
    Contributor

    @WahidBitar, I wanted to see the KeyValueData in order to better help you.
    But yes, it won't solve your main problem (
    Now I see next:

    1. In this moment you Type KeyValueData has been detect Mapster as Class not as Record.
    2. Activate Setter from Property Key possible linked from this bug in Class adapter I described it from this issue Mapster.tool MapToConstructor reassigns a object property despite assigning it in the constructor  #633.

    You use Record only as immutable type ? Or you used in this time mutable behavior from update.

    Example:

    _destination = new KeyValueData();
    var result = _Input.adapt(_desination)
    var checkresult = object.ReferenceEquals(_result, _desination) //  return true. _result is not modified copy _destination.
    
  6. DocSvartz commented on Oct 10, 2023

    @DocSvartz
    Contributor

    Hello @WahidBitar , In version Mapster 7.4.0 and Mapster Tool 8.4.0 (Net 7.0) it working

    from your last example: not Error

     public class GeneralMapper : IRegister
     {
         public void Register(TypeAdapterConfig config)
         {
             config.NewConfig<Enum?, KeyValueData?>()
             .MapWith(s => s == null ? null : new KeyValueData(s));
         }
    }
    
    

    Result of generation:

    public partial class FooMapper : TEstConsole.Mapster.IFooMapper
    {
        public TEstConsole.clases.KeyValueData MapToEntity(System.Enum s)
        {
            return s == null ? null : new TEstConsole.clases.KeyValueData(s);
        }
    }
    
    
    
  7. WahidBitar commented on Oct 10, 2023

    @WahidBitar
    Author

    thank you so much @DocSvartz
    till now I don't use the Mapster Tool, but I am using the Mapster 7.4.0 and don't know why this config didn't work for me

    config.NewConfig<Enum?, KeyValueData?>()
                .MapWith(s => s == null ? null : new KeyValueData(s.ToString(), Enums.ResourceManager));
    

    so I ended up with my workaround solution

  8. DocSvartz commented on Oct 10, 2023

    @DocSvartz
    Contributor

    I get you Error Result. Try found problem :)

  9. DocSvartz commented on Oct 13, 2023

    @DocSvartz
    Contributor

    @WahidBitar @andrerav

    Looks like I made a fix from this problem.

    @andrerav i can integrated this fix from my PR #634 or is it better to open a new PR?

    Upd:
    I'll open a new PR, I got a solution, but I don't like it :). Perhaps someone can improve it.

  10. andrerav commented on Oct 15, 2023

    @andrerav
    Member

    @DocSvartz Yes I prefer a separate PR :) Thanks for all your recent contributions, amazing work!

  11. added a commit that references this issue on Oct 15, 2023
  12. added a commit that references this issue on Jan 2, 2025
  13. stagep commented on Jan 7, 2025

    @stagep

    In order to identify issues that are still active, we are closing issues that we believe are either resolved or are dormant. If your issue is still active then please reopen. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions