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);
Related
I have a following endpoint:
#GetMapping("/{campaign}")
#SneakyThrows
public S3StringHolder downloadRawFactDataS3(#PathVariable Integer campaign) {
String selectDataQuery = new RawFactSelectTemplate(campaign).translate().getSqlQuery();
//todo: find some way to do it on object mapper level
return new StringHolder(service.downloadDataFilledTemplate(campaign, selectDataQuery));
}
StringHolderClass
#NoArgsConstructor
#AllArgsConstructor
#Data
public class StringHolder {
private String fileS3Id;
}
I use StringHolder only because i need to return here not just a simple string with service.downloadDataFilledTemplate(campaignId, selectDataQuery) method call result, but a json which will look like:
{
fileS3Id: "hereSomeText"
}
Is there some possible good-looking ways to avoid usage of StringHolder class still preserving the structure of output JSON?
To avoid wrapping your String in a Class you could make your controller return a Map<String, String> and then return:
return Collections.singletonMap("key", myText)
I have a weird behavior using SpringDataCouchbase maybe you can help me to understand.
Context
I'm using Spring-data-couchbase (v.3.1.9.RELEASE) with SpringBoot 2.
My application has an entity with a LocalDate field like
#Getter
#Setter
class MyEntity {
private LocalDate fieldName;
... Other basic/primitive fields
}
I have configured the basic converters to deal with LocalDates in the CouchbaseConfig bean.
#Configuration
#EnableCouchbaseRepositories(basePackages = {"com.example.repository.model"})
public class CouchbaseConfig extends AbstractCouchbaseConfiguration {
#Override
public CustomConversions customConversions() {
List<?> converters = Arrays.asList(
LocalDateTimeToStringConverter.INSTANCE,
StringToLocalDateTimeConverter.INSTANCE,
LocalDateToStringConverter.INSTANCE,
StringToLocalDateConverter.INSTANCE);
return new CouchbaseCustomConversions(converters);
}
#WritingConverter
public enum LocalDateToStringConverter implements Converter<LocalDate, String> {
INSTANCE;
#Override
public String convert(LocalDate source) {
return source.format(DateUtils.SHORT_DATE_FORMATTER);
}
}
#ReadingConverter
public enum StringToLocalDateConverter implements Converter<String, LocalDate> {
INSTANCE;
#Override
public LocalDate convert(String source) {
return LocalDate.parse(source, DateUtils.SHORT_DATE_FORMATTER);
}
}
The save and find operations work without problems using the CrudRepository but now I have to do a dynamic query using the CouchbaseTemplate in a CustomRepository.
The problem
I need to use template.findByN1QLProjection(queryWithParameters, MyProjection.class) to retrieve only those fields required from MyEntity but findByN1QLProjection method uses translationService.decodeFragment(json.toString(), entityClass) which is implemented by JacksonTranslationService like
public <T> T decodeFragment(String source, Class<T> target) {
try {
return objectMapper.readValue(source, target);
}
catch (IOException e) {
throw new RuntimeException("Cannot decode ad-hoc JSON", e);
}
}
The problem is: the ObjectMapper used by decodeFragment method is a new one created inside the class that doesn't use the converters from CouchbaseConfig class and as no JavaTimeModule is configured the exception com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of java.time.LocalDate is thrown.
After some time I figured out the problem and I configured a different translationService in the CouchBaseConfig with a custom object mapper which solved the error.
Question
Why entities in SpringDataCouchbase are handled in a different way when you use template findOperations than when you use CrudRepository?
Am I doing something wrong?
Thanks for your time.
I have to deserialize my Java Object and I can't use static reference i.e TypeReference.
Like,
mentioned in here
So, I am left with is generating the right Javatype using type factory, but somehow I am not able to get the syntax right.
Aforementioned are my Classes and Interfaces.
public interface Request {}
public interface Response {}
public class MyRequest implements Request {
int id;
//Getter //Setter
}
public class MyResponse implements Response {
int id;
//Getter //Setter
}
public class UberObject<S extends Request, T extends Response> implements Serializable {
private S request;
private T response;
//Getter//Setter
}
public class UberObjectWithId<S extends Request, T extends Response> extends UberObject<S, T> {
private int id;
//Getter //Setter
}
UberObjectWithId typereferencedObject =
objectmapperobject.readValue(serialisedUberObjectWithId,
new TypeReference<UberObjectWithId<MyRequest, MyResponse>>() {});
The above approach works but I can't use TypeReference because of the limitation mentioned in the above link.
I tried using the Typefactory to retrieve the JavaType but I am not able to get the syntax right.
JavaType type = mapper.getTypeFactory().constructParametrizedType(UberObjectWithId.class,
UberObject.class, Request.class, Response.class);
but the call fails
objectmapperobject.readValue(serialisedUberObjectWithId, type);
How can I resolve my particular problem?
I fixed it by using the correct syntax to generate the JavaType
JavaType type = mapper.getTypeFactory().constructParametrizedType(UberObjectWithId.class,
UberObject.class, MyRequest.class, MyResponse.class);
It requires concrete classes while deserializing.
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));
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