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;
Related
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?
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;
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());
I have a spring app in which I am using the #JsonFormat annotation to deserialize a date format. But when I sent an array of elements my entire payload fails even if one of the entries have an invalid date.
Is there a way I can surpass this by gracefully handling this exception, by either replacing the failed date with a default value or ignoring that array entry.
jackson.version: 2.7.5,
spring.version: 5.0.0.RELEASE
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
private Date date;
You could write a custom deserializer for your class where you set a default value in case something goes wrong. Something like:
public class MyJsonDateDeserializer extends JsonDeserializer<Date>
{
#Override
public Date deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
String date = jsonParser.getText();
try {
return format.parse(date);
} catch (ParseException e) {
return new Date();
}
}
}
Then on your class you could do something like:
class MyClass {
//...Fields
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
#JsonDeserialize(using = MyJsonDateDeserializer.class)
private Date date;
//...Fields
}
You could also add #JsonIgnoreProperties(ignoreUnknown = true) over your class if you know that its value is not necessary always.
I have the following code:
import javax.xml.datatype.XMLGregorianCalendar;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public class Test {
public static void main(String[] args) {
print("");//Prints null
print("2013-06-14T01:23:47.547+0000"); //Prints the date
print("&&&&AD");//Throws error
}
private static void print(String dateString) {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false );
try {
String x = "{\"dateTime\": \""+dateString+"\"}";
Foo type = mapper.readValue(x, Foo.class);
System.out.println("Datetime is " + type.getDateTime());
} catch (Exception e) {
e.printStackTrace();
}
}
private static class Foo {
private XMLGregorianCalendar dateTime;
public XMLGregorianCalendar getDateTime() {
return dateTime;
}
public void setDateTime(XMLGregorianCalendar dateTime) {
this.dateTime = dateTime;
}
}
}
When the String value is blank "", then Jackson treats the value as null, but when I put some invalid value such as "&&&&AD", it tries to convert it to XML date Time and throws error.
The error I get is:
com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not construct instance of javax.xml.datatype.XMLGregorianCalendar from String value '&&&&AD': not a valid representation (error: Failed to parse Date value '&&&&AD': Can not parse date "&&&&AD": not compatible with any of standard forms ("yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "EEE, dd MMM yyyy HH:mm:ss zzz", "yyyy-MM-dd"))
I would like to see the same behavior for blank value. How do I do it?
Is there a way to configure Jackson to fail for blank value?
You have to implement new deserializer for XMLGregorianCalendar type. It would look like this:
class XMLGregorianCalendarDeserializer extends GregorianCalendarDeserializer {
private static final long serialVersionUID = 1L;
#Override
public XMLGregorianCalendar deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
try {
return super.deserialize(jp, ctxt);
} catch (JsonProcessingException e) {
return null;
}
}
}
After that you have to define deserializer in POJO class:
class Foo {
#JsonDeserialize(using = XMLGregorianCalendarDeserializer.class)
private XMLGregorianCalendar dateTime;
...
}