RequireExplicitMapping is not working as expected.
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();
}
@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 ();
}
@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?
@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?
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.
@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.
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();
}
@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
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.
@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 :)
(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.”
@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; }
}
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.
@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.
Yes, I agree that this's outside the scope of RequireExplicitMapping, and it should be handled by another configuration.