How to map values from map to object based on containsKey? - java

I have a map of values like so:
Map<String, Object> values = Map.of("name", "myName", "address", null);
I want to update an object like this:
class User {
String name;
String address;
String country;
}
Now I want the fields in User to be overwritten only if the source map has the key defined. So the address field should be set to null (because there's an explicit mapping to null), but the country field should not be changed (because there's no "country" key in the map).
This is similar to what nullValuePropertyMappingStrategy = IGNORE does, but not quite, as the check is a map.containsKey check instead of a standard null check.
Can I extend MapStruct so it can do this?
My MapStruct code:
#Mapper
interface MyMapper {
#Mapping(target = "name", expression = "java( from.getMap().get(\"name\") )")
#Mapping(target = "address", expression = "java( from.getMap().get(\"address\") )")
#Mapping(target = "country", expression = "java( from.getMap().get(\"country\") )")
To get(MapWrapper from, #MappingTarget To to);
}

MapStruct can't do this out of the box.
However, you could wrap your Map into a Bean. So something like this:
public class MapAccessor{
private Map<String, Object> mappings;
public MapAccessor(Map<String, Object> mappings) {
this.mappings = mappings;
}
public Object getAddress(){
return this.mappings.get("address");
}
public boolean hasAddress(){
return this.mappings.containsKey("address");
}
...
}
And then you could a normal mapper mapping WrappedMap to your targetbean and using the NullValuePropertyMappingStrategy..
Note: your mapper is a lot simpler than as well..
#Mapper( nullValuePropertyMappingStrategy = NullValueProperertyMappingStrategy.IGNORE )
interface MyMapper {
To get(MapAccessor from, #MappingTarget To to);
}

Related

Problem mapping classes with different attributes with MapStruct

I'm trying to make a mapping using MapStruct but I don't know how to deal with the fields from one to the other.
I have the classes below:
class DataDomain {
private List<Domain> data;
}
class Domain {
private String codDist;
private String numFun;
private String txtJust;
private Boolean valPar;
private LocalDateTime dateHr;
private Integer numPn;
}
class DataEntity {
private String codDist;
private String numFun;
private List<ParEntity> pares;
}
class ParEntity {
private String numFun;
private String txtJus;
private String indValPar;
private String dateHr;
private String numPn;
}
interface ParOutMapper{
ParOutMapper INSTANCE = Mappers.getMapper(ParOutMapper.class);
#Mapping(target = "data", source = "entity")
DataDomain map(DataEntity entity);
Domain toDomain(DataEntity entity);
default List<Domain> toList(DataEntity entity) {
return entity != null ? singletonList(toDomain(entity)) : new ArrayList<>();
}
default DataEntity map(DataDomain domain) {
return domain != null
&& domain.getData() != null
&& !domain.getData().isEmpty() ? toEntity(domain.getData().get(0)) : null;
}
DataEntity toEntity(Domain domains);
List<Domain> toDomainList(List<DataEntity> domainList);
}
That's what I've done so far, but it's giving divergence in the mapping because both have different structures and I ended up getting lost in how to apply their origin and destination field to field.
If possible and someone knows how to do it in an interesting correct way I would be very grateful.
I would suggest the following solution
#Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR,
componentModel = "spring",
collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED,
builder = #Builder(disableBuilder = true))
public interface ParOutMapper {
#Mapping(target = "data", source = "entity")
DataDomain map(DataEntity entity);
#Mapping(target = "txtJust", source = "pares", qualifiedByName = "txtJust")
#Mapping(target = "valPar", source = "pares", qualifiedByName = "valPar")
#Mapping(target = "dateHr", source = "pares", qualifiedByName = "dateHr")
#Mapping(target = "numPn", source = "pares", qualifiedByName = "numPn")
Domain toDomain(DataEntity entity);
default List<Domain> toList(DataEntity entity) {
return entity != null ? singletonList(toDomain(entity)) : new ArrayList<>();
}
default DataEntity map(DataDomain domain) {
return domain != null
&& domain.getData() != null
&& !domain.getData().isEmpty() ? toEntity(domain.getData().get(0)) : null;
}
#Mapping(target = "pares", ignore = true)
DataEntity toEntity(Domain domains);
List<Domain> toDomainList(List<DataEntity> domainList);
#AfterMapping
default DataEntity valuesToList(Domain domains, #MappingTarget DataEntity dataEntity){
ParEntity parEntity = new ParEntity();
parEntity.setDateHr(domains.getDateHr().toString()); // alternative call custom entity to list mapping here !
parEntity.setTxtJus(domains.getTxtJust());
parEntity.setNumPn(domains.getNumPn().toString());
parEntity.setNumFun(domains.getNumFun());
parEntity.setIndValPar(domains.getValPar().toString());
dataEntity.setPares(List.of(parEntity));
return dataEntity;
}
#Named("txtJust")
default String mapTxtJust(List<ParEntity> pares) {
return pares.get(0).getTxtJus(); // or custom mapping logic here
}
#Named("valPar")
default Boolean mapValPar(List<ParEntity> pares) {
return Boolean.valueOf(pares.get(0).getIndValPar()); // or custom mapping logic here
}
#Named("dateHr")
default LocalDateTime mapDateHr(List<ParEntity> pares) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
return LocalDateTime.parse(pares.get(0).getDateHr(),formatter); // or custom mapping logic here
}
#Named("numPn")
default Integer mapNumPn(List<ParEntity> pares) {
return Integer.valueOf(pares.get(0).getNumPn()); // or custom mapping logic here
}
}
Since you tagged your question with spring-boot i assume you are using it. Therefore i would suggest to use the provided component model by mapstruct in its configuration
I am unsure how you want to do your mapping of list to entitiy or entity to list. With my approach you can do it value by value or with the entire list. Both workes either way.
The solution compiles and workes for DataEntity toEntity(Domain domains); and Domain toDomain(DataEntity entity); i did not recognize any other problems since mapstruct is able to generate the required mappings.

is there a way to extract Mapstruct mapping?

Let says I have a basic case where I map an object to an other, I need to declare the mapping in the Mapper class.
For validation I may want to remember the original field name and his corresponding mapping.
Is there a way to do it with mapstruct without having to do a mapping "manually"
here is an example of what I would expect.
public class PersonDemo {
public static void main(String[] args) {
final PersonMapper mapper = Mappers.getMapper(PersonMapper.class);
Person person = mapper
.toPerson(new PersonDto()
.setFirstName("John")
.setName("Doe"));
System.out.println(person);
System.out.println("Expected " + toPersonDesc("firstName") + " to be firstName");
System.out.println("Expected " + toPersonDesc("name") + " to be lastName");
}
#Mapper
public interface PersonMapper {
#Mapping(target = "lastName", source = "name")
Person toPerson(PersonDto dto);
}
//expected method. to be generated somehow from the mapping.
static String toPersonDesc(String fieldName) {
switch (fieldName) {
case "name": return "lastName";
case "firstName": return "firstName";
}
return null;
}
#Data
#Accessors(chain = true)
public static class Person {
private String lastName;
private String firstName;
}
#Data
#Accessors(chain = true)
public static class PersonDto {
private String name;
private String firstName;
}
}
It's impossible with Mapstruct. There's another question similar to yours: How to provide MapStruct Mapping annotation mapping meta data at runtime. The answer there describes "some" approach to the problem, but it's very limited - it simply scrapes the information from the #Mapping annotations (no support for implicit mappings).
It would be quite useful to have this though - you should propose this feature to the Mapstruct team.

How to fill in map inside target object by copying values from source object using -MapStruct?

I am new to Mapstruct. I have a scenario where, in my target object I have a java map with key value pair<String,String> and I have to fill this map using source objects inner object properties/data member values.
My code is something like bellow(dummy code):
public class Student {
public String name;
public String rollNo;
public Map<String, String> marks;
}
public class ExamResult{
public String stud_name;
public String Stud_rollNo;
public Marks marks;
}
public class Marks{
public Integer English;
public Integer Maths;
public Integer Science;
}
How would I manually achieve the same thing like bellow:
Student target;
ExamResult source;
target.setName(source.stud_name);
target.setRollNo(source.Stud_RollNo);
target.marks.put("ENGLISH",source.marks.english_marks);
target.marks.put("MATHS",source.marks.math_marks);
target.marks.put("SCIENCE",source.marks.science_marks);
For direct property mapping I have found code, but not sure how I can map the values to be filled in the marks map.
I had given a thought to use java expression to fill in target map values, but didn't found any documentation or such example of expressions using for target object.
I was thinking to use like bellow but not sure it will work:
#Mapping(source = "stud_name", target = "name")
#Mapping(source = "Stud_RollNo", target = "rollNo")
#Mapping(source = "source.marks.english_marks",target = "java( marks.put(\"ENGLISH\",source.marks.english_marks )")
#Mapping(source = "source.marks.math_marks",target = "java( marks.put(\"MATHS\",source.marks.math_marks )")
#Mapping(source = "source.marks.science_marks",target = "java( marks.put(\"SCIENCE\",source.marks.science_marks )")
Student doConvert(ExamResult src)
Any help, any suggestion or any workaround is appreciated.
Thanks in Advance.
Using expressions in target is not allowed for MapStruct, that's why you couldn't make it work.
Mapping into maps from objects is also not supported yet, we have different issues requesting this feature.
What I would suggest is to use the automatic mapping as much as possible when you can and then resort to #AfterMapping when MapStruct can't do that. So in your case something like:
#Mapper
public interface StudentMapper {
#Mapping(source = "stud_name", target = "name")
#Mapping(source = "Stud_RollNo", target = "rollNo")
#Mapping(target = "marks", ignore = true) // mapped in #AfterMapping
Student doConvert(ExamResult src)
#AfterMapping
default void addStudentMarks(ExamResult result, #MappingTarget Student student) {
student.marks = new HashMap<>();
student.marks.put("ENGLISH", result.marks.ENGLISH);
student.marks.put("MATHS", result.marks.MATHS);
student.marks.put("SCIENCE", result.marks.SCIENCE);
}
}
I would write a Custom mapper method to convert the Marks into Map Object
#Mapping(source = "stud_name", target = "name")
#Mapping(source = "Stud_rollNo", target = "rollNo")
Student doConvert(ExamResult examResult);
static Map<String,String> mapMarks(Marks marks) {
Map<String,String> marksMap = new HashMap<>();
marksMap.put("ENGLISH", String.valueOf(marks.getEnglish()));
marksMap.put("MATHS", String.valueOf(marks.getMaths()));
return marksMap;
}
In case the map elements are too big to mention, Jackson Library could be used which can dynamically create the map with reference name as Key and Object value as value
#Mapping(source = "stud_name", target = "name")
#Mapping(source = "Stud_rollNo", target = "rollNo")
Student doConvert(ExamResult examResult);
ObjectMapper mapper = new ObjectMapper();
static Map<String,String> mapMarks(Marks marks) {
return mapper.convertValue(marks, Map.class);
}
what about this:
#Mapper
public interface MyMapper {
#Mapping( target = "name", source = "stud_name")
#Mapping( target = "rollNo", source = "stud_rollNo")
// marks will be mapped based on name equality
Student map( ExamResult result);
// mapstruct sees an unmapped property (although it only has a getter), marks
#Mapping( target = "marks", ignore = true )
MarksWrapper toWrapper(Marks marks );
// handwritten method, just to do the mapping
default Map<String, Integer> toMap( MarksWrapper wrapper) {
return wrapper.getMarks();
}
// this class does the conversion
class MarksWrapper {
private Map<String, Integer> marks = new HashMap<>( );
public void setEnglish( Integer mark) {
marks.put( "ENGLISH", mark );
}
public void setMaths( Integer mark ){
marks.put( "MATH", mark );
}
public void setScience( Integer mark ) {
marks.put( "SCIENCE", mark );
}
public Map<String, Integer> getMarks() {
return marks;
}
}
}
Note: I used a Map<String,Integer> Map<String,String> but the idea is the same..

How to map native SQL query to dto object in hibernate?

I have the following native SQL query:
Select E.id AS ID, E.desc AS DESCRIPTION FROM TEMP E
And the dto class:
private int id;
private String description;
/* getter and setter */
How to get a list of dto class??
What I would probably do to maximize reuse would be to write my own ResultTransformer that you could instantiate prior to running your query and as a part of this implementation, you're required to provide it with the appropriate mapping information.
// construct the transformer and register mappings
MappedResultTransformertransformer = new MappedResultTransformer(DtoClass.class);
transformer.map( "ID", "id" );
transformer.map( "DESCRIPTION", "description" );
// apply the transformer
session.createQuery( ... ).setResultTransformer( transformer ).list();
Here's an example of how the transformer might look.
public class MappedResultTransformer extends BasicTransformerAdapter {
final Map<String, String> fieldMappings = new HashMap<>();
final Class<?> clazz;
public MappedResultTransformer(Class<?> clazz) {
this.clazz = clazz;
}
public void map(String alias, String property) {
fieldMappings.put( alias, property );
}
#Override
public Object transformTuple(Object[] tuple, String[] aliases) {
Object result = clazz.newInstance();
for ( int i = 0; i < aliases.length; ++i ) {
Object tupleValue = tuple[ i ];
String alias = aliases[ i ];
String propertyName = fieldMappings.get( alias );
if ( propertyName != null ) {
// use reflection to set the value of 'propertyName' on 'result'
}
}
return result;
}
}
The beauty here is that this class is completely reusable, it isn't tied to any specific class or query. You then could extend upon it and add support for nested properties, etc perhaps.
try this
Query query = getSession.createSQLQuery("...")
.addScalar("ID")
.addScalar("DESCRIPTION")
.setResultTransformer(Transformers.aliasToBean(dto.class));
List<dto> list = query.list();
dto class
#Entity
#Table(name="your database table")
public class DTO {
#Id
private int id
#Column(name="description_name_on_table)
private String description
..getter and setter
}

How can I convert a Map<String, String> with json values to a POJO using jackson?

I had the need to convert java objects to Map<String, String> for a REST api wrapper I am writing. Any fields that were complex objects needed to be serialized to json. I figured out how to do that like this:
public static Map<String, String> toContentMap(Object object) throws JsonProcessingException {
Map<String, Object> objectParamMap = MAPPER.convertValue(object, new TypeReference<Map<String, Object>>() {});
Map<String, String> contentMap = new HashMap<>();
for (Entry<String, Object> entry : objectParamMap.entrySet()) {
String key = entry.getKey();
String value = MAPPER.writeValueAsString(entry.getValue());
contentMap.put(key, StringUtils.strip(value, "\""));
}
return contentMap;
}
Now I need a way to get from this Map<String, String> representation back to the pojo object. Is this possible to do using mostly jackson apis?
Edit:
I guess I wasn't clear. I know the POJO I am going to/from. But it should be generic and work for any basic POJO.
An example:
class MyObject {
String fieldA;
Long fieldB;
MyOtherObject fieldC;
List<String> fieldD;
}
class MyOtherObject {
String fieldA;
String fieldB;
}
MyObject object = new MyObject("valueA", 20L,
new MyOtherObject("valueA", "valueB"),
Lists.newArrayList("array1", "array2"));
Map<String, String> contentMap = toContentMap(object);
/*
"fieldA" -> "valueA"
"fieldB" -> "20"
"fieldC" -> "{\"fieldA\":\"valueA\",\"fieldB\":\"valueB\"}"
"fieldD" -> "[\"array1\",\"array2\"]"
*/
MyObject newObject = fromContentMap(contentMap);
assertEquals(object, newObject)
Based on your comment
it should work for any POJO I ask it to
I think you are looking for the #JsonAnyGetter and #JsonAnySetter annotations, which will assign the values that it can to the POJO (which you need to specify at deserialization... it can't be generic), but will stick anything not parsed into a Map<String, Object>.
Note, it can't be Map<String, String> because JSON contains more than just Strings as its values.
For example, take the JSON
{
"uid": 1,
"username": "steve",
"email": "steve#example.com"
}
You can generate a Jackson POJO that looks like so.
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"uid",
"username",
"email"
})
public class User {
#JsonProperty("uid")
private Integer uid;
#JsonProperty("username")
private String username;
#JsonProperty("email")
private String email;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("uid")
public Integer getUid() {
return uid;
}
#JsonProperty("uid")
public void setUid(Integer uid) {
this.uid = uid;
}
#JsonProperty("username")
public String getUsername() {
return username;
}
#JsonProperty("username")
public void setUsername(String username) {
this.username = username;
}
#JsonProperty("email")
public String getEmail() {
return email;
}
#JsonProperty("email")
public void setEmail(String email) {
this.email = email;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
You can use
ObjectMapper mapper = new ObjectMapper();
String jsonInString = "{'name' : 'rahul'}";
//JSON from String to Object
User user = mapper.readValue(jsonInString, User.class);
or
User user = mapper.convertValue(map, User.class);
if you are converting from json/map to custom object. You can also pass type information to the serialization/deserialization process so that jackson is aware of the internals. Please read more about that here.
We can't do it I am afraid. When an object gets serialized into JSON String, it loses it's class type. So, while deserializing a string into object, parser doesn't know which class to deserialize the object into. However, there are a couple of ways to do it:
Store type info in another map:
Create a new map like this:
Map<String, Class> metaData = new HashMap<>();
It will store the type name along with Class. So, while derializing, we can lookup class type from this map and pass it to readValue method.
Use #Type annotation of ObjectMapper: (only useful if all the objects extend same base class)
Jakson has a feature called Polymorphic Deserialization which will help deserializing into object if all the classes inherit common base class. Have a look at the documentation here.

Categories

Resources