Skip to content

RequireExplicitMapping is not working as expected. #783

Closed

Description

@JasonGrass

When converting between two class, both of which have a property named MyProperty with different types (one is string and the other is int),
I have configured RequireExplicitMapping = true.

I expected an exception to occur during the Compile phase, indicating that I should explicitly configure the conversion between these two properties. However, that did not happen. The error only occurs at runtime if the conversion fails.

Is there any way to avoid this situation?

Mapster version: 7.4.0

// PatientInformation
public string MyProperty { get; set; } = "";

// PatientInfoViewObject
public int MyProperty { get; set; }
  public static void ConfigureMappings()
  {
      TypeAdapterConfig.GlobalSettings.RequireExplicitMapping = true;
      TypeAdapterConfig.GlobalSettings.RequireDestinationMemberSource = true;

      ConfigurePatientInfoMappings();

      //  expect an exception here
      TypeAdapterConfig.GlobalSettings.Compile();

      // test code
      var info = new PatientInformation();
      info.MyProperty = "test";
      var viewObject = info.Adapt<PatientInfoViewObject>();
  }

  private static void ConfigurePatientInfoMappings()
  {
      TypeAdapterConfig<PatientInformation, PatientInfoViewObject>.NewConfig();

      TypeAdapterConfig<PatientInfoViewObject, PatientInformation>.NewConfig();
  }

Activity

  1. DocSvartz commented on Mar 20, 2025

    @DocSvartz
    Contributor

    @JasonGrass in 7.4.0 RequireExplicitMapping = true work only for mapping to Class.

    You create custom config here private static void ConfigurePatientInfoMappings() That's why it doesn't work.
    Int is Struct - apparently this is the main reason why it doesn't work.

    This needs some thought, but could be improved.

    Here's a case where it works.
    And then it really works as you expected

    [TestMethod]
    public void RequireExplicitMappingWorkFromClass()
    {
        TypeAdapterConfig.GlobalSettings.RequireExplicitMapping = true;
        TypeAdapterConfig.GlobalSettings.RequireDestinationMemberSource = true;
    
        ConfigurePatientInfoMappings();
        
        TypeAdapterConfig.GlobalSettings.Compile(); //  get Exception- > Implicit mapping is not allowed (check GlobalSettings.RequireExplicitMapping) and no configuration exists.
    // because not config for mapping MyString to Myint
        var info = new Source783();
        info.MyProperty.Value = "123";
        var viewObject = info.Adapt<Destination783>();
    
    }
    
    private static void ConfigurePatientInfoMappings()
    {
        TypeAdapterConfig<Source783, Destination783>.NewConfig();
    }
    
    public class Source783
    {
        public MyString MyProperty { get; set; } = new ();
    }
    
    public class Destination783
    {
        public MyInt MyProperty { get; set; } = new ();
    }
    
    public class MyString
    {
        public string Value { get; set; }
    }
    
    public class MyInt
    {
        public int Value { get; set; }
    }
    

    or for

    public class Source783
    {
        public int MyProperty { get; set; } 
    }
    
    public class Destination783
    {
        public string MyProperty { get; set; } = new ();
    }
    
  2. DocSvartz commented on Mar 20, 2025

    @DocSvartz
    Contributor

    @JasonGrass If your goal is to make sure that all properties are mapping, then you can use the following config

    TypeAdapterConfig<Source783, Destination783>.NewConfig()
        .IgnoreNonMapped(true);
    

    But then you have to manually map all the Properties.

    But as I understand it, this is not quite what you need?

  3. DocSvartz commented on Mar 20, 2025

    @DocSvartz
    Contributor

    @andrerav @stagep can add the following setting:

    TypeAdapterConfig.GlobalSettings.RequireExplicitMappingPrimitive = true;

    But the side effect of using it is that the User will need to manually register all configurations used for mapping Mapster Primitive types. (Except for mapping itself to int->int ... and others)

    What do you think about this?

  4. stagep commented on Mar 20, 2025

    @stagep

    Is this setting up a catch 22 scenario, in that the user needs to know about the setting in order to know to setup primitive to primitive mappings? I use Mapster.Tool so I catch any mapping issues at compile time because I want to avoid mapping exceptions at runtime. Is adding another global setting just setting up the potential for more missing configuration?

    Does using

    TypeAdapterConfig.GlobalSettings.RequireExplicitMapping = true;
    

    requires the user to setup the primitive mappings? If this is the case then should a tooltip be added on this property informing the user that mapping of primitive types is required when this property is set to true. This might be a good opportunity to provide tooltips and enhance the documentation regarding settings that have major changes in how mapping works. A section on Global Settings on the readme would be helpful.

  5. DocSvartz commented on Mar 21, 2025

    @DocSvartz
    Contributor

    @stagep

    Is this setting up a catch 22 scenario, in that the user needs to know about the setting in order to know to setup primitive to primitive mappings?

    I think the bigger pitfall is simply extending the current RequireExplicitMapping action to primitive types.
    (As far as I understand, the current tests are made with the condition that this setting does not apply to them).
    Also Wiki contains a description that this setting applies only to Classes

    I use Mapster.Tool so I catch any mapping issues at compile time because I want to avoid mapping exceptions at runtime. Is adding another global setting just setting up the potential for more missing configuration?

    By default, this will always be off RequireExplicitMappingPrimitive = false; and does not change the current behavior.

    In the state RequireExplicitMappingPrimitive = true, this will be equivalent to RequireExplicitMapping = true or .IgnoreNonMapped(true). ("Only manual mapping mode" )

    In the sense that either manual mapping will be needed
    or just manual registration of mapping of a bundle of two primitive types

    TypeAdapterConfig<string, int>.NewConfig();
    // or
    TypeAdapterConfig<Source783, Destination783>.NewConfig()
    .Map(dest=>dest.MyProperty,src=> int.Parse(src.MyProperty));
    
    

    provided that the User really understands why this setting was activated

    requires the user to setup the primitive mappings?

    And yes, this should be added to the WIKI and a description of the setting in the code.

  6. JasonGrass commented on Mar 21, 2025

    @JasonGrass
    Author

    My initial requirements and related ideas

    Initial Requirements:

    1 Mapster should only assist me in automatically handling property mappings that have exactly the same name and type.
    2 If either the Source or Destination has properties that the other does not, the user needs to manually configure these properties to be ignored; otherwise, an exception should be thrown.
    3 If a property exists in both the Source and Destination with the same name but different types (even if it's int and double), the user should manually configure the conversion operation for this property; otherwise, an exception should be thrown.

    Rationale for These Considerations

    In simple terms: to prevent possible operational errors by other team members.
    The fundamental idea is that for all potential sources of error, explicit configuration is required to eliminate any ambiguity.

    For example:

    1 A property was added in the Source or Destination but was forgotten on the other side.
    2 The type of a property in the Source and Destination is inconsistent, while in reality, they should be consistent.

    class Source783
    {
        // Only automatically handle properties that have the same type and name.
        public string Name { get; set; } = "";
    
        // I expect there to be a property called RequireSourceMember,
        // which, when set to true, will throw an exception during compilation,
        // unless the user manually configures to ignore this property.
        public string PropertyOnlyInSource { get; set; } = "";
    
        public int Value { get; set; }
    }
    
    class Destination783
    {
        // Only automatically handle properties that have the same type and name.
        public string Name { get; set; } = "";
    
        // if RequireDestinationMemberSource = true
        // Mapster will throw an exception when compile
        // this is what we want
        public string PropertyOnlyInDestination { get; set; } = "";
    
        public double Value { get; set; }
    }
    
    public static void ConfigureMappings()
    {
        TypeAdapterConfig.GlobalSettings.RequireExplicitMapping = true;
        TypeAdapterConfig.GlobalSettings.RequireDestinationMemberSource = true;
    
        TypeAdapterConfig.GlobalSettings.RequireSourceMember = true; // maybe
        TypeAdapterConfig.GlobalSettings.RequireExplicitMappingPrimitive = true; // maybe
    
        TypeAdapterConfig<Source783, Destination783>.NewConfig();
    
        TypeAdapterConfig.GlobalSettings.Compile();
    }
  7. DocSvartz commented on Mar 21, 2025

    @DocSvartz
    Contributor

    @JasonGrass

    1 Mapster should only assist me in automatically handling property mappings that have exactly the same name and type.

    3 If a property exists in both the Source and Destination with the same name but different types (even if it's int and double), the user should manually configure the conversion operation for this property; otherwise, an exception should be thrown.

    Yes, from Primitive type it will work as you intended. RequireExplicitMappingPrimitive = true;

    2 If either the Source or Destination has properties that the other does not, the user needs to manually configure these properties to be ignored; otherwise, an exception should be thrown.
    This is a topic for a separate discussion, and it already exists.

    The main problem here is that the value may be obtained from not the Property of the SourceType at all.

    In essence, the source Type itself acts as a context for creating or modifying an Instance of the DestinationType.

    TypeAdapterConfig<Source783, Destination783>.NewConfig()
    .Map(dest=>dest.MyProperty,src=> 42);
    
    TypeAdapterConfig<Source783, Destination783>.NewConfig()
    .Map(dest=>dest.MyProperty,src=> Return42());
    

    Therefore, at the moment there is only a check for the presence of sources for all Members of the DestinationType.

    TypeAdapterConfig.GlobalSettings.RequireDestinationMemberSource = true;

    Perhaps Validation will help you? This is included in the latest pre-releases

  8. JasonGrass commented on Mar 21, 2025

    @JasonGrass
    Author

    Thank you for your detailed and patient response.
    As it stands, there doesn't seem to be a particularly elegant way to intercept the "same name but different types" situation.

    I look forward to the early release of the RequireExplicitMappingPrimitive configuration.

  9. DocSvartz commented on Mar 26, 2025

    @DocSvartz
    Contributor

    @JasonGrass and all Users of RequireExplicitMapping

    When using RequireExplicitMapping = true , do you expect that mapping a class to itself
    (result = source.Adapt<SourceType>()) will cause an error?

    Does this behavior make practical sense?

    These are questions for the subsequent unification of the behavior of RequireExplicitMapping and RequireExplicitMappingPrimitive
    in terms of mapping types onto themselves :)

  10. JasonGrass commented on Mar 27, 2025

    @JasonGrass
    Author

    (Sourse.Adapt()) will cause an error?

    I tend to throw an error because this is indeed a confusing operation.

    There's a possibility that the user intends to clone an object instance using this approach.
    If Mapster supports cloning functionality, it could provide a message in the exception indicating: “If you want to clone, please use the xxx method.”

  11. DocSvartz commented on Mar 27, 2025

    @DocSvartz
    Contributor

    @JasonGrass I probably forgot to mark this part as code.

    I meant this case similar to this one.

    result = _source.Adapt<SourceType>()
    
    or 
    
    _source.Adapt(_source2) // where _source and _source2 is Type of SourceType
    
    

    When during mapping a class instance is mapped to another instance of the same class.

    Here is a more realistic example

    TypeAdapterConfig.GlobalSettings.RequireExplicitMapping = true;
    TypeAdapterConfig<Source783, Destination783>.NewConfig();
    
     TypeAdapterConfig.GlobalSettings.Compile(); //  get Exception  because not config for mapping MyString to MyString
    
    
    public class Source783
    {
        public MyString MyProperty { get; set; } = new ();
    }
    
    public class Destination783
    {
        public MyString MyProperty { get; set; } = new ();
    }
    
     public class MyString
     {
        public string Value { get; set; }
     }
    
    
    
  12. JasonGrass commented on Mar 27, 2025

    @JasonGrass
    Author
    public class Source783
    {
        public MyString MyProperty { get; set; } = new ();
    }
    
    public class Destination783
    {
        public MyString MyProperty { get; set; } = new ();
    }
    
     public class MyString
     {
        public string Value { get; set; }
     }

    In this case, the expected effect of object mapping should be a deep copy of the object, so that MyString and MyString are different instances. If this is the case, then no exception need be thrown.

    However, if it is simply a reference assignment, I believe an exception should be thrown, with an error message explaining the reason: the default behavior is reference assignment, not deep copy.

    Users should have the option in their custom mapping to decide whether to use reference assignment or deep copy.

  13. DocSvartz commented on Mar 27, 2025

    @DocSvartz
    Contributor

    @JasonGrass I see what you mean. But that's outside the scope of RequireExplicitMapping.
    It was done to check if the mapping configuration was registered.

    In this case, the expected effect of object mapping should be a deep copy of the object, so that MyString and MyString are different instances. If this is the case, then no exception need be thrown.

    However, if it is simply a reference assignment, I believe an exception should be thrown, with an error message explaining the reason: the default behavior is reference assignment, not deep copy.

    If you are interested in this, open a discussion on adding this feature.

  14. JasonGrass commented on Mar 28, 2025

    @JasonGrass
    Author

    Yes, I agree that this's outside the scope of RequireExplicitMapping, and it should be handled by another configuration.

  15. linked a pull request that will close this issueBump all PR #815on Oct 1, 2025
  16. DocSvartz commented on Mar 19, 2026

    @DocSvartz
    Contributor

    This should be fixed in new release versions.
    If your issue is still active, please reopen it or create new issue. 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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions