Mapping 2 String fields to OffsetDateTime with MapStruct - java

I have the following DTO which has 2 fields that must be converted to OffsetDateTime:
#Data
public class AppointmentDTO {
private String id;
#NotNull
private String startTime;
#NotNull
private String endTime;
#NotNull
private String timeZoneStart;
#NotNull
private String timeZoneEnd;
// other fields
}
to
#Data
#Document
#NoArgsConstructor
#AllArgsConstructor
public class Appointment {
#Id
private String id;
private String timeZoneStart;
private String timeZoneEnd;
private OffsetDateTime startTime;
private OffsetDateTime endTime;
private OffsetDateTime createdTime;
// other fields
}
In order to convert, I need the DTO's timeZone fields plus a DateTimeFormatter. So, my attempt is this:
#Component
#Mapper(componentModel = "spring")
public interface IAppointmentMapper {
#Mapping(target = "createdTime", ignore = true)
Appointment convertAppointmentDTOToAppointment(AppointmentDTO dto, #Context OffsetDateTimeMapper offsetDateTimeMapper);
}
public class OffsetDateTimeMapper {
private String startTime;
private String endTime;
private String timeZoneStart;
private String timeZoneEnd;
private final DateTimeFormatter dateTimeFormatter;
public OffsetDateTimeMapper(DateTimeFormatter dateTimeFormatter) {
this.dateTimeFormatter = dateTimeFormatter;
}
#BeforeMapping
public void beforeStartTimeMapping(AppointmentDTO dto) {
this.startTime = dto.getStartTime();
this.timeZoneStart = dto.getTimeZoneStart();
}
#BeforeMapping
public void beforeEndTimeMapping(AppointmentDTO dto) {
this.endTime = dto.getEndTime();
this.timeZoneEnd = dto.getTimeZoneEnd();
}
#AfterMapping
public void startTimeMap(#MappingTarget Appointment appointment) {
LocalDateTime localDateTime = LocalDateTime.parse(startTime, dateTimeFormatter);
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of(timeZoneStart));
appointment.setStartTime(zonedDateTime.toOffsetDateTime());
}
#AfterMapping
public void endTimeMap(#MappingTarget Appointment appointment) {
LocalDateTime localDateTime = LocalDateTime.parse(endTime, dateTimeFormatter);
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of(timeZoneEnd));
appointment.setEndTime(zonedDateTime.toOffsetDateTime());
}
}
When I build, however, I get the following error: Error:(22,17) java: Can't map property "java.lang.String startTime" to "java.time.OffsetDateTime startTime". Consider to declare/implement a mapping method: "java.time.OffsetDateTime map(java.lang.String value)". If I put a default map() method on the interface, I don't get the error, but I don't have the proper #Context required.
/*default OffsetDateTime map(String value) {
LocalDateTime localDateTime = LocalDateTime.parse(value);
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneOffset.UTC);
return zonedDateTime.toOffsetDateTime();
}*/

Your approach is interesting. In order to work properly you would have to explicitly ignore the properties that you don't want MapStruct to automatically map.
In this case add:
#Mapping(target = "startTime", ignore = true)
#Mapping(target = "endTime", ignore = true)
However, in your case I would try to use Mapping method selection based on qualifiers and use the source parameters as source.
So something like:
#Component
#Mapper(componentModel = "spring")
public interface IAppointmentMapper {
#Mapping(target = "createdTime", ignore = true)
#Mapping(target = "startTime", source = "dto", qualifiedByName = "startTime")
#Mapping(target = "endTime", source = "dto", qualifiedByName = "endTime")
Appointment convertAppointmentDTOToAppointment(AppointmentDTO dto, #Context DateTimeFormatter dateTimeFormatter);
#Named("startTime")
default OffsetDateTime mapStartTime(AppointmentDTO dto, #Context DateTimeFormatter dateTimeFormatter) {
LocalDateTime localDateTime = LocalDateTime.parse(dto.getStartTime(), dateTimeFormatter);
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of(dto. getTimeZoneStart()));
return zonedDateTime.toOffsetDateTime()
}
#Named("endTime")
default OffsetDateTime mapEndTime(AppointmentDTO dto, #Context DateTimeFormatter dateTimeFormatter) {
LocalDateTime localDateTime = LocalDateTime.parse(dto.getEndTime(), dateTimeFormatter);
ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneId.of(dto. getTimeZoneEnd()));
return zonedDateTime.toOffsetDateTime()
}
}
Note: #Named is org.mapstruct.Named.

Related

Problem with mapstruct java: Can't generate mapping method from iterable type to non-iterable type

I am new to java programming trying to do dto mapping,to implement data filtering
#Data
#FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class Request {
List<EventType> eventType;
String eventRef;
List<Status> status;
LocalDate DateFrom;
LocalDate DateTo;
}
public class FilterDto {
#JsonProperty("eventType")
#Valid
private List<EventTypeEnum> eventType = null;
#JsonProperty("eventRef")
private String eventRef;
#JsonProperty("status")
#Valid
private List<StatusEnum> status = null;
#JsonProperty("DateFrom")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private LocalDate DateFrom;
#JsonProperty("DateTo")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private LocalDate DateTo;
map struct func
Request map(List<FilterDto> filter);
I call this mapper in the controller
Request request = registryMapper.map(regPageReqDto.getFilter());
regPageReqDto generated class in swagger, which contains FilterDto

Spring RequestBody cannot parse datetime timezone correctly

I have a simple rest service to store time range, however, Spring cannot parse datetime format with timezone correctly.
the Entity is
#Data
#Entity
public class TimeRange {
#Setter(AccessLevel.NONE)
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = true)
private LocalDateTime startTime;
#Column(nullable = true)
private LocalDateTime endTime;
}
The controller is:
#PostMapping(path = "/time", consumes = "application/json", produces = "application/json")
public Boolean setTime(#RequestBody TimeRange timeRange) {
timeRangeRepository.save(timeRange);
return true;
}
and the actuall request is
url = f'http://localhost/api/time'
data = {
"startTime": "2019-12-03T19:58:29.047820+08:00",
"endTime": "2019-12-04T19:58:29.047820+08:00"}
resp = requests.post(url, json=data, timeout=10)
pprint(resp.json())
spring reported an error said:
esolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error:
Cannot deserialize value of type `java.time.LocalDateTime` from String "2019-12-
03T19:58:29.047820+08:00": Failed to deserialize java.time.LocalDateTime:
(java.time.format.DateTimeParseException) Text '2019-12-03T19:58:29.047820+08:00' could not be
parsed, unparsed text found at index 26; nested exception is
com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type
java.time.LocalDateTime from String "2019-12-03T19:58:29.047820+08:00": Failed to deserialize
java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '2019-12-
03T19:58:29.047820+08:00' could not be parsed, unparsed text found at index 26
at
You have a date with offset, if all your date comes in the same format you can create a custom deserializer like this
public class CustomLocalDateTimeDeserializer extends StdDeserializer<LocalDateTime> {
private static final long serialVersionUID = 1L;
public CustomLocalDateTimeDeserializer () {
this(null);
}
protected CustomLocalDateTimeDeserializer (Class<?> vc) {
super(vc);
}
#Override
public LocalDateTime deserialize(JsonParser arg0, DeserializationContext arg1)
throws IOException, JsonProcessingException {
return LocalDateTime.parse(arg0.getValueAsString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
}
and the annotate your fields with #JsonDeserialize
#JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
private LocalDateTime startTime;
#JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
private LocalDateTime endTime;
And if you want to serialize your dates with the same format, you have to create a custom serializer
Annotate your LocalDateTime fields with:
#JsonSerialize(using = LocalDateTimeSerializer.class)

Spring Data Redis - Issue while storing Date

I am using Spring Boot + Spring data Redis example to save Date into the Redis Cache. Although I used #DateTimeFormat #JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd"), but still persistance happening is long value. Look like its a millisecond.
Can somebody guide if I need to set extra configurations to persist date like yyyy-MM-dd.
HGETALL users:1
1) "_class"
2) "com.XXX.entity.User"
3) "userId"
4) "1"
5) "name"
6) "John"
7) "createdDate"
8) "1542043247352"
Entity classes:
#Builder
#Data
#AllArgsConstructor
#NoArgsConstructor
#RedisHash("users")
public class User {
#Id
private Long userId;
private String name;
#DateTimeFormat
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date createdDate;
private List<Group> groups;
}
UPDATE-1:: As per suggestion I implemented, but still not working
CustomDateSerializer.java
#Component
public class CustomDateSerializer extends JsonSerializer<Date> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy");
#Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
throws IOException, JsonProcessingException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}
Custom Interface
#Retention(RetentionPolicy.RUNTIME)
public #interface MyJsonFormat {
String value();
}
Model class
#MyJsonFormat("dd.MM.yyyy")
#JsonSerialize(using = CustomDateSerializer.class)
private Date createdDate;
I'd advise using LocalDateTime (or LocalDate if you prefer) instead. You can then annotate your fields with
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime createdAt;
using jackson's jsr310 add on:
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
By Using Custom Serializer, this can be solved. Ref #https://kodejava.org/how-to-format-localdate-object-using-jackson/#comment-2027
public class LocalDateSerializer extends StdSerializer<LocalDate> {
private static final long serialVersionUID = 1L;
public LocalDateSerializer() {
super(LocalDate.class);
}
#Override
public void serialize(LocalDate value, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeString(value.format(DateTimeFormatter.ISO_LOCAL_DATE));
}
}
POJO:
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
private LocalDate createdDate;

ModelMapper and LocalDate - Spring Boot

currently I'm trying to map a dto to a class that also contains LocalDate attribute. Merely I have no success here and the local date field always remains null. So I built a short example, where I followed the pretty helpful hints from Modelmapper to convert from String to LocalDate
So I have a ModelMapper class like this :
#Bean
public ModelMapper createMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.createTypeMap(String.class, LocalDate.class);
Provider<LocalDate> localDateProvider = new AbstractProvider<LocalDate>() {
#Override
public LocalDate get() {
return LocalDate.now();
}
};
Converter<String, LocalDate> toStringDate = new AbstractConverter<String, LocalDate>() {
#Override
protected LocalDate convert(String source) {
DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDate localDate = LocalDate.parse(source, format);
return localDate;
}
};
modelMapper.getTypeMap(String.class, LocalDate.class).setProvider(localDateProvider);
modelMapper.addConverter(toStringDate);
return modelMapper;
}
Furthermore I have a POJO that only has 2 fields, an id and a local date (w/o getters and setters for the sake of readability.
public class JsonLocalDate {
private Long id;
#JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate ld;
public Long getId() {
return id;
}
And I created a test class where I tried to mock the json part by a LinkedHashMap as it comes in the web services I have implemented :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.NONE)
public class ModelMapperTest {
#Autowired
ModelMapper mapper;
String jsonLd = "2018-06-11";
LinkedHashMap<String, String> lhm;
#Before
public void init() {
lhm = new LinkedHashMap<>();
lhm.put("id", "1");
lhm.put("ld", jsonLd);
}
#Test
public void checkModelMapper() {
assertNotNull(mapper);
Collection<TypeMap<?, ?>> c = mapper.getTypeMaps();
assertNotNull(c);
for (TypeMap<?, ?> typeMap : c) {
System.out.println("TypeMap : " + typeMap.getConverter().toString());
}
}
#Test
public void testLocalDate() {
LocalDate ld = mapper.map(jsonLd, LocalDate.class);
assertNotNull(ld);
assertEquals(ld, LocalDate.parse(jsonLd,
DateTimeFormatter.ofPattern("yyyy-MM-dd")));
}
#Test
public void testLocalDateInObject() {
JsonLocalDate jld = mapper.map(jsonLd, JsonLocalDate.class);
assertNotNull(jld);
assertEquals(jld.getLd(), LocalDate.parse(jsonLd,
DateTimeFormatter.ofPattern("yyyy-MM-dd")));
}
}
testLocalDate where I just map the String to LocalDate works fine, while the testLocalDateInObject fails.
Has anybody any idea how I have to deal with LocalDate fields to get them mapped ?
Thanks in advance !
Cheers
Joern
The test failed is because you are trying to map a String to an Object and ModelMapper doesn't know how to map a String to an Object.
So you should try Property Mapping
modelMapper.typeMap(String.class, JsonLocalDate.class)
.addMapping(src -> src, JsonLocalDate::setLd);

Spring, parsing string to LocalDateTime

I've got model and field like this:
#Element(name = "TIMESTAMP")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime date;
In response I received:
<TIMESTAMP>2016-05-04T13:13:42.000</TIMESTAMP>
but during parsing xml to model I have error:
"message": "org.simpleframework.xml.core.PersistenceException: Constructor not matched for class java.time.LocalDateTime",
I also tried with:
#Element(name = "TIMESTAMP")
#DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS")
private LocalDateTime date;
and this still doesn't work. Any Idea ? I am using springframework.xml lib.
The problem is by default simplexml lib doesn't know how to serialize/deserialize new Java8 date types.
In order to succeed you need to use custom converter.
Example entity (see the special #Convert annotation)
public class Entity {
#Element(name = "TIMESTAMP")
#Convert(LocalDateTimeConverter.class)
private LocalDateTime date;
// omitted
}
Special converter
public class LocalDateTimeConverter implements Converter<LocalDateTime> {
public LocalDateTime read(InputNode node) throws Exception {
String name = node.getValue();
return LocalDateTime.parse(name, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
public void write(OutputNode node, LocalDateTime input) {
String value = input.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
node.setValue(value);
}
}
Usage
Strategy strategy = new AnnotationStrategy();
Persister persister = new Persister(strategy);
Entity serializedEntity = persister.read(Entity.class, xmlInputStream);
Full source is available on GitHub

Categories