Spring RequestBody cannot parse datetime timezone correctly - java

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)

Related

How to deserialize date '2017-01-01T00:00:59.000UTC'

Trying to deserialize date with specific pattern from json file.
Object which I want to receive from json file:
#Data
public class MyClass {
#DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'UTC'")
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime date;
}
Json file:
{
"date" : "2017-01-01T00:00:59.000UTC"
}
Code example how I want to receive it:
ObjectMapper mapper = new ObjectMapper();
MyClass clazz = mapper.readValue(new File("MyFile.json"), MyClass.class);
Actual result:
com.fasterxml.jackson.databind.exc.InvalidFormatException:
Cannot deserialize value of type `java.time.LocalDateTime` from String "2017-01-01T00:00:59.000UTC":
Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException)
Text '2017-01-01T00:00:59.000UTC' could not be parsed, unparsed text found at index 23
at [Source: (File); line: 2, column: 11] (through reference chain: com.example.MyClass["date"])
How to deserialize current date pattern?
The date format that you are using is incorrect.
Instead of: yyyy-MM-dd'T'HH:mm:ss.SSS'UTC'
it should be: yyyy-MM-dd'T'HH:mm:ss.SSSz
Secondly, you need to use #JsonFormat to specify the date format.
#JsonFormat which is defined in jackson-databind package gives you more control on how to format Date and Calendar values according to SimpleDateFormat.
By using this, the POJO MyClass would look something like this:
#Data
public class MyClass {
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSz", timezone = "UTC")
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime date;
}
Now, if you try to deserialize using:
ObjectMapper mapper = new ObjectMapper();
MyClass clazz = mapper.readValue(new File("MyFile.json"), MyClass.class);
System.out.println(myClass);
Then the process would go through, producing a result something like this:
MyClass{date=2017-01-01T00:00:59.000}
Your date is in incorrect format (with UTC as text simply appended), but you can solve it by custom formatter.
public class Test {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
MyClass localDateTime = objectMapper.readValue("{\"date\":\"2017-01-01T00:00:59.000UTC\"}", MyClass.class);
System.out.println(localDateTime.date);
}
#Data
public static class MyClass {
#JsonDeserialize(using = CustomDeserializer.class)
private LocalDateTime date;
}
public static class CustomDeserializer extends LocalDateTimeDeserializer {
public CustomDeserializer() {
super(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
protected CustomDeserializer(LocalDateTimeDeserializer base, Boolean leniency) {
super(base, leniency);
}
#Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String substring = jsonParser.getText().substring(0, jsonParser.getText().indexOf("U"));
return LocalDateTime.parse(substring, _formatter);
}
}
}
Try removing #JsonDeserialize. (In any case, you are trying to deserialize your date into LocalDateTime but it has time zone info, you would need to try ZonedDateTime or OffsetDateTime). And change the line
#DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'UTC'")
to
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
Here is the link to the question that has a full answer for you: Spring Data JPA - ZonedDateTime format for json serialization

Mapping 2 String fields to OffsetDateTime with MapStruct

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.

Failed to parse "Date" from JSON

I have the following JSON string in REST response:
"09:41:50 CET"
For the corresponding POJO mapper class has a Date type for this field. So I've tried Jackson and GSON to map JSON to Java Object, but both failed with the following messages:
GSON: java.text.ParseException: Failed to parse date ["09:41:50 CET"]: Invalid number: 09:4
Jackson: InvalidFormatException: Cannot deserialize value of type `java.util.Date` from
String "09:41:50 CET": not a valid representation
Sadly I cannot modify in the POJO class the type to string or anything else, because I get those POJO classes from mvn dependency.
Try with this:
public static void main(String[] args) throws ParseException {
String jsonStr = "{ \"date\" : \"09:41:50 CET\" }";
Gson gson = new GsonBuilder().setDateFormat("HH:mm:ss").create();
JsonElement element = gson.fromJson (jsonStr, JsonElement.class);
OnlyDate date =gson.fromJson(element, new TypeToken<OnlyDate>(){}.getType());
System.out.println(date.getDate());
}
My example DTO is:
public class OnlyDate implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
#SerializedName("date")
private Date date ;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
You have to specify the dateFormat of your gson Element
Not sure what kind of rest you have however if you are using spring rest you can do it by implementing custom Converter check the example at https://www.baeldung.com/spring-mvc-custom-data-binder.
Since Jackson v2.0, you can use #JsonFormat annotation directly on Object members;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss", timezone="CET")
private Date date;

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;

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