How to deserialise JSON datetime with nanosecond precision using gson - java

I'm trying to deserialise a JSON object using gson, but having issues when it comes to dates. The date is deserialised from the JSON object, but because the value in the JSON object is in nanoseconds, the value I get is slightly off of the expected value.
See the following code
JSONClass
public class JSONClass {
private Date timestamp;
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
}
Main
public class GsonTestApplication {
public static void main(String[] args) {
final Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss.SSS").create();
final String responseJSON = "{ \"timestamp\":\"2017-11-09 11:07:20.079364+00\" }";
final JSONClass foo = gson.fromJson(responseJSON, new TypeToken<JSONClass>(){}.getType());
System.out.println(foo.getTimestamp().toString());
}
}
The output of the application is
Thu Nov 09 11:08:39 GMT 2017
When I expect it to be
Thu Nov 09 11:07:20 GMT 2017
I don't care about the nanosecond precision, so I'm happy for this to be truncated, but as I don't have control over the JSON format, I'm not sure the best way to do this.
How can I get gson to deserialise the date correctly?

This is an issue with the precision available in Date, and with Java 8 it's best to use LocalDateTime. This also means that you will need a TypeAdapter as Gson doesn't work very well with LocalDateTime. This type adapter needs to be registered with Gson to deserialize (and potentially serialize) LocalDateTime objects from Strings.
Something like the below should give you what you need.
JSONClass
public class JSONClass {
private LocalDateTime timestamp;
public LocalDateTime getTimestamp() {
return timestamp;
}
public void setTimestamp(LocalDateTime timestamp) {
this.timestamp = timestamp;
}
}
LocalDateTimeDeserialiser
static class LocalDateTimeDeserializer implements JsonDeserializer<LocalDateTime> {
private DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
LocalDateTimeDeserializer(String datePattern) {
this.formatter = DateTimeFormatter.ofPattern(datePattern);
}
#Override
public LocalDateTime deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return LocalDateTime.parse(json.getAsString(), formatter);
}
Main
public class GsonTestApplication {
public static void main(String[] args) {
final Gson gson = new GsonBuilder().(LocalDateTime.class, new LocalDateTimeDeserializer("yyyy-MM-dd HH:mm:ss.SSSSSSx")).create();
final String responseJSON = "{ \"timestamp\":\"2017-11-09 11:07:20.079364+00\" }";
final JSONClass foo = gson.fromJson(responseJSON, new TypeToken<JSONClass>(){}.getType());
System.out.println(foo.getTimestamp().toString());
}
}

Related

Gson Removing Timestamp From Date while Conversion

I am using to convert JSON String to POJO Class using Gson. In Pojo the attribute are of java.util.Date type. While gson maps Json String to Pojo objects it is removing timestamp.
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.S'Z'").create();
ClassBaseModel baseModel = gson.fromJson(request, ClassBaseModel.class);
Attribute Original Value in String Json is - "orderDate": "2021-12-01T07:16:31Z"
After It is converted into POJO - 2021-12-01
Expected is - 2021-12-01T07:16:31Z
I am not sure what wrong I am doing. Can somebody please point out.
While Deserializing you have to register deserializer type in gson using registerTypeAdapter
Here is how I have tried to do so, and also
Request class:
class OrderInfoRequest {
String orderDate;
public OrderInfoRequest(String orderDate) {
this.orderDate = orderDate;
}
}
Main Mapper class
class OrderInfo {
OffsetDateTime orderDate;
public OffsetDateTime getOrderDate() {
return orderDate;
}
public void setOrderDate(OffsetDateTime orderDate) {
this.orderDate = orderDate;
}
#Override
public String toString() {
return "OrderInfo{" +
"orderDate=" + orderDate +
'}';
}
}
Sample code
public class DateMain {
public static void main(String[] args) {
OrderInfoRequest orderInfoRequest = new OrderInfoRequest("2021-12-01T07:16:31Z");
Gson gson = new GsonBuilder()
.registerTypeAdapter(OffsetDateTime.class, (JsonDeserializer<OffsetDateTime>) (json, typeOfT, context) -> OffsetDateTime.parse(json.getAsString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME))
.create();
String requestJson = gson.toJson(orderInfoRequest);
System.out.println("Request json");
System.out.println(requestJson);
OrderInfo orderInfo = gson.fromJson(requestJson, OrderInfo.class);
System.out.println("After parsing pojo");
System.out.println(orderInfo);
System.out.println("printing full date: " + orderInfo.orderDate.format(DateTimeFormatter.ofPattern("yyyy/MM/dd, hh:mm")));
}
}
Also as told by #Jens avoid using java.util.Date class from today onwards if you haven't.

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

How to serialize Long epoch date format into date using JSONObject

I have json string payload having date in epoch (long) format.But I have to convert that into yyyyMMddHHmmss format.I'm using custom serializers where I can apply that on particular field.But the serialization is not able to apply on that field.
Test.java
private static String json = "{
"dcCountryCode": "US",
"orderDate": 1517855400000
}";
#JsonSerialize(using = CustomLongSerializer.class)
private static Long date;
public static void main(String[] args) {
JSONObject obj = new JSONObject(json);
String country = obj.getString("dcCountryCode");
date = obj.getLong("orderDate");
System.out.println(country);
System.out.println(date);
}
CustomLongSerializer.java
package com.company;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import java.text.SimpleDateFormat;
// This is for Custom Date serializer
public class CustomLongSerializer extends StdSerializer<Long> {
protected CustomLongSerializer(Class<Long> t) {
super(t);
}
protected CustomLongSerializer() {
this(Long.class);
}
private static final long serialVersionUID = 1L;
#Override
public void serialize(Long value, JsonGenerator gen, SerializerProvider provider) throws IOException {
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
gen.writeString(df.format(value));
}
}
Expected Out put in yyyyMMddHHmmss format.
But still returning epoch date format.
Can anyone help me with this.
It may be implemented in a simpler way by disabling SerializationFeature.WRITE_DATES_AS_TIMESTAMPS and setting DateFormatter in the mapper:
public class TestDate {
private String dcCountryCode;
private Date date;
// getters/setters
}
// test class
String json = "{\n" +
" \"dcCountryCode\": \"US\",\n" +
" \"date\": 1517855400000\n" +
" }";
ObjectMapper mapper = new ObjectMapper()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.setDateFormat(new SimpleDateFormat("yyyyMMddHHmmss"));
TestDate test = mapper.readValue(json, TestDate.class);
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(test));
Output:
{
"dcCountryCode" : "US",
"date" : "20180205203000"
}

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.

Jackson and empty string

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;
...
}

Categories