FutureOrPresent not working as expected with present date - java

Facing this issue as mentioned here. #FutureOrPresent doesn't let present date pass through.
#FutureOrPresent(message = "From Date Must Be Of Future Or Present")
#JsonDeserialize(using = MyCustomDeserializer.class)
#JsonSerialize(using = MyCustomSerializer.class)
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern= "dd-MM-yyyy", timezone ="UTC")
public class MyCustomDeserializer extends JsonDeserializer<Instant> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd-MM-yyyy").withZone(ZoneOffset.UTC);
#Override
public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
LocalDate date = LocalDate.parse(p.getText(), fmt);
Instant instant = date.atStartOfDay(ZoneId.of("UTC")).toInstant();
return instant;
}
}
public class MyCustomSerializer extends JsonSerializer<Instant> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd-MM-yyyy").withZone(ZoneOffset.UTC);
#Override
public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
String str = fmt.format(value);
gen.writeString(str);
}
I get this error:
"errorCode": "BAD_REQUEST", "message": "From Date Must Be Of Future Or Present",
Pojo field has future or present validation but present is not getting applied as expected. Can someone please help understanding the issue?

Related

Jackson deserialize date string to Long

Can Java Jackson deserialize a json string date into a Java Long field (milliseconds from epoch)?
This is an example of json field to be deserialized:
"timestamp": "2022-01-02T03:04:05Z",
and this is the same field in the Java class, with the current annotations:
#JsonFormat(shape = JsonFormat.Shape.NUMBER, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", timezone = "UTC")
#JsonProperty("timestamp")
#JsonPropertyDescription("blah, blah\r\n")
public Long timestamp;
However, an exception happens:
com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot
deserialize value of type java.lang.Long from String
"2022-01-02T06:49:05Z": not a valid Long value
Any hint? Thanks.
The answer by Maurice is correct, it only suffers from using the notoriously troublesome and long outdated SimpleDateFormat and Date classes. Also the deserialize method is much simpler without them:
public class LongTimestampDeserializer extends StdDeserializer<Long> {
public LongTimestampDeserializer() {
this(null);
}
public LongTimestampDeserializer(Class<?> vc) {
super(vc);
}
/** #throws InvalidFormatException If the timestamp cannot be parsed as an Instant */
#Override
public Long deserialize(JsonParser parser, DeserializationContext ctxt)
throws IOException {
String timestamp = parser.getText();
try {
return Instant.parse(timestamp).toEpochMilli();
}
catch (DateTimeParseException dtpe) {
throw new InvalidFormatException(
parser, dtpe.getMessage(), timestamp, Long.class);
}
}
}
The way I understand it the deserializer should throw some subclass of JsonProcessingException in case of a parsing error. InvalidFormatException is a suitable subclass in this case.
Use a custom date deserializer like this one:
public class CustomDateDeserializer extends StdDeserializer<Long> {
private SimpleDateFormat formatter =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
public CustomDateDeserializer() {
this(null);
}
public CustomDateDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Long deserialize(JsonParser jsonparser, DeserializationContext context)
throws IOException, JsonProcessingException {
String date = jsonparser.getText();
try {
return formatter.parse(date).toInstant().toEpochMilli();
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
Next annotate your field with #JsonDeserialize(using = CustomDateDeserializer.class).
#JsonDeserialize(using = CustomDateDeserializer.class)
public Long timestamp;

How i can force JSON dates to not accept integer in java

How i can force JSON date with Java to use a particular pattern and don't accept Integers, for example :
{
"birthday": 1
}
should not be accepted.
I tried
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
private LocalDate birthday;
but still accept numbers.
First create a class custom localDate deserializer
public class LocalDateDserializer extends StdDeserializer {
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
public LocalDateDserializer() {
this(null);
}
public LocalDateDserializer(final Class<?> vc) {
super(vc);
}
#Override
public LocalDate deserialize(final JsonParser jsonparser, final DeserializationContext context)
throws IOException, JsonProcessingException {
final String date = jsonparser.getText();
try {
return formatter.parse(date).toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
} catch (final ParseException e) {
throw new RuntimeException(e);
}
}
}
Use the annotation
#JsonDeserialize(using = LocalDateDserializer.class)
private LocalDate birthday;

How to send/receive a date object to be in a different format in JSON and not in timestamp?

I have the following POJO which I use to send out as messages to rabbitmq:
public class MyMessage {
private String id;
private String name;
private Date createdDt;
#JsonCreator
public MyMessage(
#JsonProperty("id") String id,
#JsonProperty("name") String name,
#JsonProperty("createdDt") Date createdDt
) {
this.id = id;
this.name = name;
this.createdDt = createdDt;
}
}
The problem with this is that when I send it using rabbitTemplate.convertAndSend(), the createdDt will be in unix timestamp in the JSON message. I need the createdDt in the JSON after serialised to be in the format of dd-MM-yyyy HH:mm:ss.
I don't want to change the createdDt property in MyMessage class to be a string in that formatted date because I may want to use the POJO else where in the code and having the date as a string is not convenient later. It also doesn't sound "right" to have the date in string just for the purpose of having it in a particular format.
When I'm receiving the message back, I also need to deserialise that string date in the format of dd-MM-yyyy HH:mm:ss back into a Date object.
How can I keep the createdDt as a Date object while sending the date in a different format when serialised and then have the string deserialised back as a date object?
If you must use java.util.Date then just add the following annotation onto the createdDt field
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss")
private Date createdDt;
I recommend not using java.util.Date but preferring the Java 8+ Time API. In that case you can import Jackson's built-in support via module com.fasterxml.jackson.datatype:jackson-datatype-jsr310 and ...
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
... will, by default, render LocalDateTime as an ISO 8601 string.
Ref: https://fasterxml.github.io/jackson-datatype-jsr310/javadoc/2.7/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.html
With that specific requirements regarding serialzation/deserialization of the field I would suggest using custom serializer/deserializer.
public class CustomDateSerializer extends StdSerializer<Date> {
 
    #Override
    public void serialize(Date value, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
        // your implementation
    }
}
public class CustomDateDeserializer extends StdDeserializer<Item> {
#Override
public Date deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// your implementation
}
}
Then you can simply mark createdDt like this:
public class MyMessage {
private String id;
private String name;
private Date createdDt;
#JsonCreator
public MyMessage(
#JsonProperty("id") String id,
#JsonProperty("name") String name,
#JsonProperty("createdDt") #JsonDeserialize(using = CustomDateDeserializer.class) #JsonSerialize(using = CustomDateSerializer.class) Date createdDt
) {
this.id = id;
this.name = name;
this.createdDt = createdDt;
}
}
This way you instruct Jackson to use your specific serializer/deserializer on a specific field.
If you would like to make the configuration to be applied on ObjectMapper level you can achieve it with module registration like that:
SimpleModule myModule = new SimpleModule();
myModule.addSerializer(Date.class, new CustomDateSerializer());
myModule.addDeserializer(Date.class, new CustomDateDeserializer());
objectMapper.registerModule(myModule);

Serializer / Deserializer for OffsetDateTime in Spring Boot

I've created a Serializer / Deserializer for OffsetDateTime in a Spring Boot v1.5.14.RELEASE app. First I create a custom constraint annotation:
#Primary
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(OffsetDateTime.class, new JsonSerializer<OffsetDateTime>() {
#Override
public void serialize(OffsetDateTime offsetDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(offsetDateTime));
}
});
simpleModule.addDeserializer(OffsetDateTime.class, new JsonDeserializer<OffsetDateTime>() {
#Override
public OffsetDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return DateUtils.convertToOffsetDateTime(jsonParser.getValueAsString());
}
});
objectMapper.registerModule(simpleModule);
return objectMapper;
}
in the response I see the value correctly formatted, but on the request I got this error
Failed to convert property value of type 'java.lang.String' to required type 'java.time.OffsetDateTime' for property 'fromDate'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#com.fasterxml.jackson.annotation.JsonFormat java.time.OffsetDateTime] for value '2019-01-01'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2019-01-01]]
and
public static OffsetDateTime convertToOffsetDateTime (String date) {
ZoneId zoneId = ZoneId.of(DateFormat.TIME_ZONE_ID);
ZoneOffset currentOffsetForMyZone = zoneId.getRules().getOffset(Instant.now());
return OffsetDateTime.of( parseLocalDate(date),LocalTime.NOON, currentOffsetForMyZone);
}
and I think the Deserializer is not even called because I added this to throw an Exception, but no exception is throw...
public OffsetDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
int m = 9 /0 ;
return DateUtils.convertToOffsetDateTime(jsonParser.getValueAsString());
}
public static LocalDate parseLocalDate(String strDate) {
return LocalDate.parse(strDate, DateFormat.DATE_FORMATTER);
}
and the bean:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class HotelData {
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private OffsetDateTime fromDate;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private OffsetDateTime toDate;
}
and
public final class DateFormat {
public static final String DATE_PATTERN = "yyyy-MM-dd";
public static final String TIME_ZONE_ID = "Africa/Brazzaville";
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter
.ofPattern(DATE_PATTERN)
.withZone(ZoneId.of(TIME_ZONE_ID));
private DateFormat(){}
}
and the problem is on testing:
mockMvc.perform(get("/hotel")
.param("hotelId", "1338767")
.param("fromDate", "2019-01-01")
.param("toDate", "2019-05-21")
.contentType(APPLICATION_JSON))
.andDo(print())
.andExpect(status().isOk());

How do I deserialize 24 hour date string to LocalDate using Jackson and RestTemplate

I have the following...
public static final String DATE_PATTERN = "yyyy-MM-dd'T'hh:mm:ss.SSSZ";
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = DATE_PATTERN)
private LocalDate created;
return this.restTemplate.postForObject(url, entity, SearchResult.class);
When I run the code it errors out with the following...
java.time.DateTimeException: Invalid value for ClockHourOfAmPm (valid values 1 - 12): 13
at java.base/java.time.temporal.ValueRange.checkValidValue(ValueRange.java:311) ~[na:na]
at java.base/java.time.temporal.ChronoField.checkValidValue(ChronoField.java:717) ~[na:na]
How do I deserialize this to a LocalDate? Regular Java7 date works fine.
The Final Solution Looks like this
public static final String DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
public class LocalDateDeserializer extends StdDeserializer<LocalDate>{
protected LocalDateDeserializer(){
super(LocalDate.class);
}
#Override
public LocalDate deserialize(JsonParser parser, DeserializationContext context) throws IOException {
return LocalDate.parse(parser.readValueAs(String.class), DateTimeFormatter.ofPattern(JiraService.DATE_PATTERN));
}
}
public class LocalDateSerializer extends StdSerializer<LocalDate> {
public LocalDateSerializer() {
super(LocalDate.class);
}
#Override
public void serialize(LocalDate value, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeString(value.format(DateTimeFormatter.ofPattern(JiraService.DATE_PATTERN)));
}
}
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
private LocalDate created;
hh is for 1-12 hour format, use HH for 0-23 hour format, see SimpleDateFormat docs. You need:
public static final String DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
Older classes like SimpleDateFormat or Calendar are lenient by default so they are silently fixing the date by moving it forward by the overflowing field offset. That's why you are not supposed to use them anymore.

Categories