I have 2 objects :
#Setter
#Getter
public class Agent {
public int userID;
public String name;
public boolean isVoiceRecorded;
public boolean isScreenRecorded;
public boolean isOnCall;
public LocalDateTime startEventDateTime;
}
public class AgentLine {
public int userID;
public String name;
public boolean isVoiceRecorded;
public boolean isScreenRecorded;
public boolean isOnCall;
public String startEventDateTime;
}
I would like to map between AgentLine to Agent. I can't use the default mapping because of the Localdatetime conversion.
I have defined :
#Bean
ModelMapper getModelMapper() {
ModelMapper modelMapper = new ModelMapper();
Converter<AgentLine, Agent> orderConverter = new Converter<AgentLine, Agent>() {
#Override
public Agent convert(MappingContext<AgentLine, Agent> mappingContext) {
AgentLine s = mappingContext.getSource();
Agent d = mappingContext.getDestination();
/* d.userID = s.userID;
d.name = s.name;*/
d.startEventDateTime = LocalDateTime.parse(s.startEventDateTime, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
return d;
}
};
modelMapper.addConverter(orderConverter);
return modelMapper;
}
In order to use it:
AgentLine line;
#Autowired
private ModelMapper modelMapper;
Agent agent = modelMapper.map(line, Agent.class);
It works , but I do not want to specify all Agent properties in the convert method, I would like to specify the startEventDateTime conversion and the rest of the properties would be mapped by default.
In addition I have tried to define :
PropertyMap<AgentLine, Agent> orderMap = new PropertyMap<AgentLine, Agent>() {
#Override
protected void configure() {
map().setName(source.name);
}
};
modelMapper.addMappings(orderMap);
but , in the mapping you can't handle the date conversion.
If I define for the mapper PropertyMap and Converter the PropertyMap is ignored.
I do not want to specify all Agent properties in the convert method, I would like to specify the startEventDateTime conversion and the rest of the properties would be mapped by default.
Do not use Converter for mapping complex objects. You should use TypeMap for such purposes. Use Converter for custom conversion (for your case String to LocalDateTime).
ModelMapper modelMapper = new ModelMapper();
Converter<String, LocalDateTime> dateTimeConverter = ctx -> ctx.getSource() == null ? null : LocalDateTime.parse(ctx.getSource(), DateTimeFormatter.ISO_LOCAL_DATE_TIME);
modelMapper.typeMap(AgentLine.class, Agent.class)
.addMappings(mapper -> mapper.using(dateTimeConverter).map(AgentLine::getStartEventDateTime, Agent::setStartEventDateTime));
Related
I am struggling with mapping List into responseDTO.getList()
My code:
MessageDTO
#Getter
#Setter
public Class MessageDTO() {
private String message;
...
}
MessagesDTO
#Getter
#Setter
public Class MessagesDTO() {
private List<> message;
}
MyConverter
public class MyConverter extends AbstractConverter<List<MessageDTO>, MessagesDTO> {
#Override
protected ChatMessagesResponseDTO convert(List<MessageDTO> source) {
MessagesDTO destination = new MessagesDTO();
destination.setMessages(source);
return destination;
}
}
Controller
...
List<MessageDTO> messages = ... // result of service and succesfull mapping entity to dto
ModelMapper mm = new ModelMapper();
Converter conv = new MyConverter();
mm.addConverter(conv);
MessagesDTO messagesDTO = mm.map(messages, MessagesDTO.class)
return messagesDTO; // always null
Any ideas why it is not working ? I am sucessfuly using modelmapper in many other places of my project even with custom TypeMap(s) and Converter(s), but cannot find a way how to map list of some type into DTO attribute which is list of that type.
This is because of type erasure. ModelMapper is unable to recognize the generic type of a List and thus does not apply your converter. I'm not sure if it is possible to achieve with classes you presented but if it is it might be quite complicated task.
One solution would be to declare class that has the type stored runtime. So like:
#SuppressWarnings("serial")
public static class MessageDTOList extends ArrayList<MessageDTO> {};
and make required changes to your converter, so to be:
public class MyConverter extends AbstractConverter<MessageDTOList, MessagesDTO> {
#Override
protected MessagesDTO convert(MessageDTOList source) {
MessagesDTO destination = new MessagesDTO();
destination.setMessages(source);
return destination;
}
}
If it is hard to get the response directly as a MessageDTOList you can always:
List<MessageDTO> messages = ... // result of service and succesfull mapping entity
MessageDTOList messagesDerived = new MessageDTOList();
messagesDerived.addAll(messages);
and then just:
MessagesDTO messagesDTO = mm.map(messagesDerived, MessagesDTO.class);
Trying to format an object before writing the result to a text file using spring batch but I don't get the expected result.
I want to use the spring API annotation format, #NumberFormat for numbers and #DateTimeFormat for date because I have several objects to format and I do not want to waste time creating formatting services
The model:
public class Type_0 {
#NumberFormat(pattern="###,###.###")
private BigDecimal typeEnregistrement= BigDecimal.valueOf(123456789.123);
#DateTimeFormat(pattern = "dd.MM.yyyy")
private Date dateCreationFichier;
#NumberFormat(pattern="###,###.###")
public BigDecimal getTypeEnregistrement() {
return typeEnregistrement;
}
#NumberFormat(pattern="###,###.###")
public void setTypeEnregistrement(BigDecimal typeEnregistrement) {
this.typeEnregistrement = typeEnregistrement;
}
#DateTimeFormat(pattern = "dd.MM.yyyy")
public Date getDateCreationFichier() {
return dateCreationFichier;
}
#DateTimeFormat(pattern = "dd.MM.yyyy")
public void setDateCreationFichier(Date dateCreationFichier) {
this.dateCreationFichier = dateCreationFichier;
}
#Override
public String toString() {
return typeEnregistrement+""+typeEmetteur+numEmetteur+"
"+TypeDest+numDest+dateCreationFichier; }
}
The writer:
#Bean
public FlatFileItemWriter<Type_0> writer() {
FlatFileItemWriter<Type_0> writer = new FlatFileItemWriter<Type_0>();
writer.setResource(new FileSystemResource("c:/upload/b2.txt"));
DelimitedLineAggregator<Type_0> delLineAgg = new DelimitedLineAggregator<>();
delLineAgg.setDelimiter(";");
writer.setLineAggregator(delLineAgg);
return writer;
}
The processor:
#Override
public Type_0 process(final Type_0 item) throws Exception {
DefaultFormattingConversionService conversionService =
new DefaultFormattingConversionService(false);
conversionService.addFormatterForFieldAnnotation(
new NumberFormatAnnotationFormatterFactory());
Type_0 t0=new Type_0();
t0=item;
DataBinder dataBinder = new DataBinder(t0);
dataBinder.setConversionService(conversionService);
return t0;
}
Expected result is: 123.456.789,123 0000125 04.24.2007
but I have in the output file: 123456789.123 0000125 2007-04-24
Class A {
private String a;
private String b;
private B innerObject;
}
Class B {
private String c;
}
In my case, String b might come in with a null value. My modelmapper configuration is like below:
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration()
.setFieldMatchingEnabled(true)
.setMatchingStrategy(MatchingStrategies.LOOSE)
.setFieldAccessLevel(AccessLevel.PRIVATE)
.setSkipNullEnabled(true)
.setSourceNamingConvention(NamingConventions.JAVABEANS_MUTATOR);
when I map the object, I get the target object with b=null value.
Trying to stay away from a strategy shown here: SO- Question
What am I missing?
Have you tried this configuration:
modelMapper.getConfiguration().setPropertyCondition(Conditions.isNotNull());
I'd rather in this way:
#Configuration
public class ModelMapperConfig {
#Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setSkipNullEnabled(true);
return modelMapper;
}
}
Looks, like it's not possible.
public <D> D map(Object source, Class<D> destinationType) {
Assert.notNull(source, "source");
Assert.notNull(destinationType, "destinationType");
return this.mapInternal(source, (Object)null, destinationType (String)null);
}
I solved it with next wrapper function.
private static <D> D map(Object source, Type destination) {
return source == null ? null : mapper.map(source, destination);
}
Check this question too Modelmapper: How to apply custom mapping when source object is null?
From the frontend I receive GET-request which contains encoded json string as one of its parameters:
http://localhost:8080/engine/template/get-templates?context=%7B%22entityType%22%3A%22DOCUMENT%22%2C%22entityId%22%3A%22c7a2a0c6-fd34-4f33-9cb8-14c2090565ea%22%7D&page=1&start=0&limit=25
Json-parameter 'context' without encoding (UUID is random):
{"entityType":"DOCUMENT","entityId":"c7a2a0c6-fd34-4f33-9cb8-14c2090565ea"}
On backend my controller's method which handle that request looks like this:
#RequestMapping(value = "/get-templates", method = RequestMethod.GET)
public List<Template> getTemplates(#RequestParam(required = false, name = "context") Context context) {
//...
}
'Context' domain class:
public class Context {
private String entityType;
private UUID entityId;
public String getEntityType() {
return entityType;
}
public void setEntityType(String entityType) {
this.entityType = entityType;
}
public UUID getEntityId() {
return entityId;
}
public void setEntityId(UUID entityId) {
this.entityId = entityId;
}
}
I believed Spring's Jackson module would automatically convert that kind of json to java object of Context class, but when I run this code it gives me exception:
org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException: Failed to convert value of type 'java.lang.String' to required type 'com.company.domain.Context'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.company.domain.Context': no matching editors or conversion strategy found
On StackOverflow I've seen similar questions, but those were about POST-requests handling (with #RequestBody annotation), which doesn't fit with GET-request.
Could you help me to solve this problem?
Thanks in advance.
I think you need to specify that your GET mapping is looking to consume JSON:
#RequestMapping(value = "/get-templates", method = RequestMethod.GET, consumes = "application/json")
public List<Template> getTemplates(#RequestParam(required = false, name = "context") Context context) {
//...
}
If this doesn't work then you can call the Jackson ObjectMapper yourself:
#RequestMapping(value = "/get-templates", method = RequestMethod.GET)
public List<Template> getTemplates(#RequestParam(required = false, name = "context") String context) {
ObjectMapper mapper = new ObjectMapper();
Context myContext = mapper.readValue(context, Context.class);
//...
}
As far as I know Spring does not have the mechanism to convert from a String to a UUID in older releases. In such case you should declare you entityId as a String and then use a converter in order to convert it to UUID.
So your Context class should be like below:
public class Context {
private String entityType;
private String entityId;
public String getEntityType() {
return entityType;
}
public void setEntityType(String entityType) {
this.entityType = entityType;
}
public String getEntityId() {
return entityId;
}
public void setEntityId(String entityId) {
this.entityId = entityId;
}
public UUID getEntityIdAsUUID() {
return convertToUUID(this.entityId);
}
// Helper Conversion String to UUID method
private UUID convertToUUID(String entityId){
return UUID.fromString(entityId);
}
}
I faced the same issue in both Jersey and Spring MVC when trying to convert the JSON String {"x":"1001822.831","y":"200716.8913"} to a object of a class called Point
Point class is as below
public class Point
{
private Double x;
private Double y;
//getters and setters
}
As per Jersey documentation, I added the below method to Point class and it worked for both Jersey and Spring MVC.
//used by jax rs & spring mvc for converting queryParam String to Point
public static Point valueOf(String json) throws IOException
{
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(json, Point.class);
}
Please refer to section 3.2 here https://jersey.github.io/documentation/latest/jaxrs-resources.html#d0e2271
I'm using Spring with Gson to object serialization.
I have model objects that use #Expose annotation e.g.:
public class Zone {
#Expose
private String name;
#Expose
private String description;
#Expose
private List<String> longList;
private String someIrrelevantVar;
}
I'm have 2 controllers which serves Zone objects list to user e.g.:
#RestController
class ZoneController {
#GetMapping(value = "fullData")
List<Zone> getFullZones() {
return zoneService.getZones();
}
}
#RestController
class SimpleZoneController {
#GetMapping(value = "simpleData")
List<Zone> getSimpleZones() {
return zoneService.getZones();
}
}
The problem is List<String> longList var - it usually has a lot of entries (String is only example, in code it could be complex object).
In my getFullZones() I want to serve to user zones with this longList but in getSimpleZones() I want ot serve zones without longList (it's not used in any way on the client side).
How to do that?
I could iterate list with zones and set longList to null but it's not very elegant solution.
I'm setting up Spring to use Gson like this:
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(createGsonHttpMessageConverter());
super.configureMessageConverters(converters);
}
private GsonHttpMessageConverter createGsonHttpMessageConverter() {
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
//.registerTypeAdapter - register some deserializers
.create();
GsonHttpMessageConverter gsonConverter = new GsonHttpMessageConverter();
gsonConverter.setGson(gson);
return gsonConverter;
}
}
Create a base class ZoneSimple and extend another class Zone extends ZoneSimple. Move the #Expose from fields to methods.
In the base class the method has no annotation. In the Zone the method is annotated.
Alternatively you can add a ProxyZone class which keeps zone instance and delegates all the calls to the instance. Annotate the Proxy as you need.
Another way is
Gson gson = new GsonBuilder()
.addSerializationExclusionStrategy(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getName().toLowerCase().contains("fieldName");
}
#Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
})
.create();
Got from the answer