Mapstruct: HashMap<String, Object> as source to Object - object

How could I use a HashMap<String, MyObjectSource> as source to an object?
Here is my target object:
public class QuantityDTO{
private Integer shoes;
private Integer pants;
}
Here is my object source:
public class Product{
private String name;
private Integer quantity;
}
Here is my map<String, Product> :
<("shoes", Product), ("pants", Product)>
I've tried to use this approach, but it's failing for me.
My Mapper:
#Mapping(target = "shoes", source = "shoes.quantity")
#Mapping(target = "pants", source = "pants.quantity")
QuantityDTO mapToQuantityDto(Map<String, Product> map);

The problem here is that Map to Bean mappings and nested source mappings do not combine that well. The problem with this is that MapStruct can not know whether the . is meant as a nested mapping identifier or if it is part of the key for the map itself. Both options are valid.
The behaviour to allow '.' as part of the key is listed here as a bug.
My suggestion would be to write out the mapping yourself with something like:
#Mapping(target = "shoes", source = "shoes")
#Mapping(target = "pants", source = "pants")
QuantityDTO mapToQuantityDto(Map<String, Product> map);
default Integer ProductToQuantity(Product product){
return product == null ? null : product.getQuantity();
}
This way the source field only contains the key, and the rest of the mapping is done at the default method.
For more complex mappings you could add another mapping annotated method to handle the complexity.
Also it would be useful to know what the message is that you get as a reason for it failing, or if you do not get a message what the mapstruct generated code looks like.

Related

MapStruct unable to generate mapper for XJC (JAXB) generated classes

I'm struggling since a couple of hours trying to get MapStruct generate a valid mapper for JAXB generated classes. The particularity of these classes is that they don't have neither setters nor adders for collections. For example:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "IndividualType", propOrder = {"addressTypes","pensionTypes"})
public class IndividualType
{
...
#XmlElement(name = "addressType")
protected List<AddressType> addressTypes;
#XmlAttribute(name = "firstName", required = true)
protected String firstName;
...
public List<AddressType> getAddressTypes()
{
if (addressTypes == null) {
addressTypes = new ArrayList<AddressType>();
}
return this.addressTypes;
}
public String getFirstName()
{
return firstName;
}
public void setFirstName(String value)
{
this.firstName = value;
}
...
}
The class avove have a getter and a setter for attributes (firstName in this example) but for collections (List here) it only has a getter. Hence it's the consumer responsibility to access via getAddressTypes(add (new AddressType(...)).
The MapStruct mapper for such a class is as follows:
#Mapper(collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE, uses = {AddressTypeMapper.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = "spring")
public interface IndividualTypeMapper
{
IndividualType toIndividualType(IndividualEntity individual);
#InheritInverseConfiguration
IndividualEntity fromIndividualType(IndividualType individualType);
}
And the MapStruct generated code is:
#Override
public IndividualEntity fromIndividualType(IndividualType individualType)
{
if ( individualType == null )
return null;
IndividualEntity individualEntity = new IndividualEntity();
individualEntity.setFirstName( individualType.getFirstName() );
...
return individualEntity;
}
In the generated code above, only the properties having a setter get initialized despite the usage of the TARGET_IMMUTABLE strategy.
Any suggestions please ? Of course, a simple constructor would perfectly do but, for some reason, people seems to prefer complicated and nonworking solutions to simple working ones and, consequently, I have to use MapStruct :-(
Many thanks in advance.
Marie-France
The reason why it is not working is due to the fact that you are using CollectionMappingStrategy.TARGET_IMMUTABLE. With that you are basically telling MapStruct my collection targets are immutable and will throw an exception if you try to modify the collection returned by the getter.
I would suggest removing the collectionMappingStrategy and see whether it works without it.

Cucumber V5-V6 - passing complex object in feature file step

So I have recently migrated to v6 and I will try to simplify my question
I have the following class
#AllArgsConstructor
public class Songs {
String title;
List<String> genres;
}
In my scenario I want to have something like:
Then The results are as follows:
|title |genre |
|happy song |romance, happy|
And the implementation should be something like:
#Then("Then The results are as follows:")
public void theResultsAreAsFollows(Songs song) {
//Some code here
}
I have the default transformer
#DefaultParameterTransformer
#DefaultDataTableEntryTransformer(replaceWithEmptyString = "[blank]")
#DefaultDataTableCellTransformer
public Object transformer(Object fromValue, Type toValueType) {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.convertValue(fromValue, objectMapper.constructType(toValueType));
}
My current issue is that I get the following error: Cannot construct instance of java.util.ArrayList (although at least one Creator exists)
How can I tell cucumber to interpret specific cells as lists? but keeping all in the same step not splitting apart? Or better how can I send an object in a steps containing different variable types such as List, HashSet, etc.
If I do a change and replace the list with a String everything is working as expected
#M.P.Korstanje thank you for your idea. If anyone is trying to find a solution for this here is the way I did it as per suggestions received. Inspected to see the type fromValue has and and updated the transform method into something like:
if (fromValue instanceof LinkedHashMap) {
Map<String, Object> map = (LinkedHashMap<String, Object>) fromValue;
Set<String> keys = map.keySet();
for (String key : keys) {
if (key.equals("genres")) {
List<String> genres = Arrays.asList(map.get(key).toString().split(",", -1));
map.put("genres", genres);
}
return objectMapper.convertValue(map, objectMapper.constructType(toValueType));
}
}
It is somehow quite specific but could not find a better solution :)

Conflicts in AutoMapper and AutoFixture

I have 2 classes, Class1 should be mapped to Class2. I do mapping with AutoMapper. I'd like to test my configuration of the mapper and for this purposes I'm using AutoFixture. Source class Class1 has property of type IList<>, destination class Class2 has a similar property but of type IEnumerable<>. To simplify test preparation I'm using AutoFixture (with AutoMoqCustomization) to initialize both source and destination objects. But after initializing property of type IEnumerable<> with AutoFixture, AutoMapper can't map the property.
Error text:
Error mapping types.
Mapping types: Class1 -> Class2 ConsoleApplication1.Class1 ->
ConsoleApplication1.Class2
Type Map configuration: Class1 -> Class2 ConsoleApplication1.Class1 ->
ConsoleApplication1.Class2
Property: Items
Could anybody help me to configure either AutoMapper or AutoFixture to make the mapping work? As a workaround I can assign null to the destination property, but I do not want to do this in the each test.
Simplified example of code:
public class AutoMapperTests
{
public static void TestCollectionsProperty()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ItemClass1, ItemClass2>();
cfg.CreateMap<Class1, Class2>();
});
var src = new Class1();
src.Items = new List<ItemClass1>()
{
new ItemClass1() { Text = "111" },
new ItemClass1() { Text = "222" }
};
var fixture = new Fixture();
var dst = fixture.Create<Class2>();
Mapper.Map(src, dst); //Error at this line of code
}
}
public class Class1
{
public IList<ItemClass1> Items { get; set; }
}
public class Class2
{
public IEnumerable<ItemClass2> Items { get; set; }
}
public class ItemClass1
{
public string Text { get; set; }
}
public class ItemClass2
{
public string Text { get; set; }
}
It's not really an AutoFixture issue per se. You can reproduce it without AutoFixture by instead creating dst like this:
var dst = new Class2();
dst.Items = Enumerable.Range(0, 1).Select(_ => new ItemClass2());
This will produce a similar error message:
Unable to cast object of type 'WhereSelectEnumerableIterator2[System.Int32,Ploeh.StackOverflow.Q45437098.ItemClass2]' to type 'System.Collections.Generic.IList1[Ploeh.StackOverflow.Q45437098.ItemClass2]'
That ought to be fairly self-explanatory: WhereSelectEnumerableIterator<int, ItemClass2> doesn't implement IList<ItemClass2>. AutoMapper attempts to make that cast, and fails.
The simplest fix is probably to avoid populating dst:
var dst = new Class2();
If you must use AutoFixture for this, you can do it like this:
var dst = fixture.Build<Class2>().OmitAutoProperties().Create();
Unless the Class2 constructor does something complex, however, I don't see the point of using AutoFixture in that scenario.
If, on the other hand, you do need dst to be populated, you just need to ensure that dst.Items is convertible to IList<ItemClass2>. One way to do that would be like this:
var dst = fixture.Create<Class2>();
dst.Items = dst.Items.ToList();
You could create a Customization to make sure that this happens automatically, but if you need help with that, please ask a new question (if you don't find one that already answers that question).
Here is a working example for your problem. As #Mark Seemann already told, Mapper.CreateMap has been deprecated, so this example is using the new structure.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ItemClass1, ItemClass2>();
cfg.CreateMap<Class1, Class2>();
});
var src = new Class1();
src.Items = new List<ItemClass1>()
{
new ItemClass1() { Text = "111" },
new ItemClass1() { Text = "222" }
};
var dest = Mapper.Map<Class1, Class2>(src);
AM requires IList because you're mapping to an existing list and that works by calling IList.Add.

NullPointerException in Custom Dstinct Mapper

i am using hazelcast 3.6.1 and implementing distinct aggregate functionality using custom mapreduce to get solr facet kind of results.
public class DistinctMapper implements Mapper<String, Employee, String, Long>{
private transient SimpleEntry<String, Employee> entry = new SimpleEntry<String, Employee>();
private static final Long ONE = Long.valueOf(1L);
private Supplier<String, Employee, String> supplier;
public DistinctMapper(Supplier<String, Employee, String> supplier) {
this.supplier = supplier;
}
#Override
public void map(String key, Employee value, Context<String, Long> context) {
System.out.println("Object "+ entry + " and key "+key);
entry.setKey(key);
entry.setValue(value);
String fieldValue = (String) supplier.apply(entry);
//getValue(value, fieldName);
if (null != fieldValue){
context.emit(fieldValue, ONE);
}
}
}
and mapper is failing with NullPointerException. and sysout statement says entry object is null.
SimpleEntry : https://github.com/hazelcast/hazelcast/blob/v3.7-EA/hazelcast/src/main/java/com/hazelcast/mapreduce/aggregation/impl/SimpleEntry.java
Can you point me the issue in the above code ? Thanks.
entry field is transient. This means that it is not serialized, so when DistinctMapperobject is deserialized on hazecalst node, it's value is null.
Removing the transient will solve the NullPointerException.
On the side note:
Why do you need this entry field? It doesn't seem to have any use.

ValueInjecter - converting some properties of source into a list in target

Just started using Value Injector: http://valueinjecter.codeplex.com/, and I have a question:
I have a Source class that has many properties. Some properties have a common suffix of "Profile". These properties are not always String, but mostly are. Target has 1 property, Dictionary<string, string>. I would like all properties that end with "Profile" to be inserted into the Dictionary<string, string> with key = PropertyName and value = Property's value. I think it can be done but the documentation isn't very clear to me. Can someone point me to the right direction? Thanks!
Sorry I have no experience with Value Injector, but if you chose to use AutoMapper you would most likely use a Custom Resolver:
Mapper.CreateMap<Source, Destination>()
.ForMember(dest => dest.Profile, opt => opt.ResolveUsing<ProfileResolver>());
Where the resolver would look something like:
public class ProfileResolver : ValueResolver<Source, Dictionary<string, string>>
{
protected override int ResolveCore(Source source)
{
var rc = new Dictionary<string, string>();
// Do some funky reflection stuff here
return rc;
}
}
Full details can be found in the custom resolver documentation.

Resources