Instant incorrectly parsing epoch string with rest api [closed] - java

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 21 days ago.
Improve this question
I have a rest API with an input value which is an epoch. However, storing as an Instant ignores the nanos, thus storing it as some date in the future (I think). I could be storing them in the wrong type also as had thought about storing them as a long either.
Example:
From the input of 683124845000 I am expecting a date and time in 1991. It is converted to +23617-05-13T13:23:20Z.
public class Booking {
private Instant epoch;
private String email;
}
The JSON input is:
{
"epoch": "683124845000",
"email": "email#email.com"
}
Currently, I just have a controller that returns OK regardless of input as I am modeling the input.
#PostMapping("/booking")
public ResponseEntity createBooking(#RequestBody Booking booking) {
return ResponseEntity.ok().build();
}

The default Instant serializer is expecting the epoch to be in seconds.
Here are the following solutions:
Use long or Long to store the epoch.
Change your API to expect seconds.
Create custom serializer / deserializer.
Custom serializer / deserializer implementation:
(Assuming you want to use milliseconds for your API since the epoch in the question seemed to be in milliseconds)
Serializer
public class CustomInstantSerializer extends JsonSerializer<Instant> {
#Override
public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
long epoch = value.toEpochMilli();
gen.writeNumber(epoch);
}
}
Deserializer
public class CustomInstantDeserializer extends JsonDeserializer<Instant> {
#Override
public Instant deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
long epoch = jp.getLongValue();
return Instant.ofEpochMilli(epoch);
}
}
Then wiring the serializer / deserializer to the field:
public class Booking {
#JsonSerialize(using = CustomInstantSerializer.class)
#JsonDeserialize(using = CustomInstantDeserializer.class)
private Instant epoch;
private String email;
}

To store the epoch value correctly, you can change the data type of the epoch field in the Booking class to long.
Example:
public class Booking {
private long epoch;
private String email;
}
Then in the controller, you can convert the string value from the request body to a long using Long.parseLong() method.
Example:
#PostMapping("/booking")
public ResponseEntity createBooking(#RequestBody Booking booking) {
booking.setEpoch(Long.parseLong(booking.getEpoch()));
return ResponseEntity.ok().build();
}

Related

What could be the reason of auto datetime conversion in java api response

I created one simple model class User. I used Util date here.
class User{
private int id;
private String name;
private Date createdAt;
}
On user post API call, I simply do setCreatedAt(new Date).
The problem is in the response, I am getting createdAt as -5.30 of the actual time. No additional time conversion method is called.
For Example, I hit the POST API user created at 28-10-2021 11:30:00 which I can see in the logs. But when it returns the response to the postman it shows 28-10-2021 06:00:00 time. There is no time conversion method in the code. I checked the return object in the return statement in debug mode even there is showing 28-10-2021 11:30:00.
I wanted to know where is this conversion happening. And how to stop this.
If it's the problem with datetime library, then which one should I use.
Extra information:
* My system timezone is in UTC.
* I am using ubuntu.
* Creating restFull APIs(JaxRs)
EDIT 1:
client and server are on the same machine(UTC timezone). For client, I am using Postman.
URL: [POST] /user
Request Body:
{
"name": "XYZ"
}
Actual Response:
{
"id": 1,
"name": "XYZ",
"createdAt: "28-10-2021 06:00:00"
}
Expected Response:
{
"id": 1,
"name": "XYZ",
"createdAt: "28-10-2021 11:30:00"
}
On user post API call, I simply do setCreatedAt(new Date).
It appears that you have not set the timezone while creating an instance of java.util.Date
By default it will set as UTC irrespective of your system timezone. You can use the https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html instead.
final TIMEZONE = ""; // need to set the timezone here
SimpleDateFormat formatter = new SimpleDateFormat("dd-M-yyyy hh:mm:ss", Locale.ENGLISH);
formatter.setTimeZone(TimeZone.getTimeZone(TIMEZONE));
String dateInString = "28-10-2021 11:30:00";
Date date = formatter.parse(dateInString);
There might be JsonFormat annotations that have timeZone issues. Please check the link for more details on the issue.jackson-data-bind issue Overriding the timezone in ObjectMapper didn't work either. You can refer the solved example by implementing a custom Date Deserializer as below:
#Component
public class CustomDateDeserializer extends StdDeserializer<Date> {
private static final long serialVersionUID = 1L;
private SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); // specify your specific timezone
public CustomDateDeserializer() {
this(null);
}
public CustomDateDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Date deserialize(JsonParser jsonparser, DeserializationContext context)
throws IOException, JsonProcessingException {
String date = jsonparser.getText();
try {
return formatter.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
Also, add the deserializer on the setter method in your bean properties.
#JsonDeserialize(using = CustomDateDeserializer.class)
public void setReturnDateTime(Date returnDateTime) {
this.returnDateTime = returnDateTime;
}

java jackson mapper convert unix timestamp to date, result wrong [duplicate]

I've got some JSON that has timestamps in seconds (i.e. a Unix timestamp):
{"foo":"bar","timestamp":1386280997}
Asking Jackson to deserialize this into an object with a DateTime field for the timestamp results in 1970-01-17T01:11:25.983Z, a time shortly after the epoch because Jackson is assuming it to be in milliseconds. Aside from ripping apart the JSON and adding some zeros, how might I get Jackson to understand the seconds timestamp?
I wrote a custom deserializer to handle timestamps in seconds (Groovy syntax).
class UnixTimestampDeserializer extends JsonDeserializer<DateTime> {
Logger logger = LoggerFactory.getLogger(UnixTimestampDeserializer.class)
#Override
DateTime deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String timestamp = jp.getText().trim()
try {
return new DateTime(Long.valueOf(timestamp + '000'))
} catch (NumberFormatException e) {
logger.warn('Unable to deserialize timestamp: ' + timestamp, e)
return null
}
}
}
And then I annotated my POGO to use that for the timestamp:
class TimestampThing {
#JsonDeserialize(using = UnixTimestampDeserializer.class)
DateTime timestamp
#JsonCreator
public TimestampThing(#JsonProperty('timestamp') DateTime timestamp) {
this.timestamp = timestamp
}
}
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="s")
public Date timestamp;
edit: vivek-kothari suggestion
#JsonFormat(shape=JsonFormat.Shape.NUMBER, pattern="s")
public Timestamp timestamp;
A very similar approach to that of #DrewStephens's which uses the Java SE TimeUnit API (introduced in JDK1.5) instead of plain String concatenation and is thus (arguably) a little bit cleaner and more expressive:
public class UnixTimestampDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
String unixTimestamp = parser.getText().trim();
return new Date(TimeUnit.SECONDS.toMillis(Long.valueOf(unixTimestamp)));
}
}
Specifying your custom deserializer (UnixTimestampDeserializer) on the affected Date field(s):
#JsonDeserialize(using = UnixTimestampDeserializer.class)
private Date updatedAt;
I had this exact issue, except my ZonedDateTime objects got turned to unix-timestamps (seconds) and I needed them in milliseconds (to initialize JS Date objects on the browser).
Implementing a custom serializer/deserializer looked like too much work for something that should be pretty straightforward, so I looked elsewhere and found that I can just configure the object mapper for the desired result.
Because my application already overrides the default ObjectMapper provided by Jersey, I just had to disable SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS.
Here's my code
#Provider
public class RPObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper defaultObjectMapper;
public RPObjectMapperProvider() {
defaultObjectMapper = new ObjectMapper();
// this turned ZonedDateTime objects to proper seconds timestamps
defaultObjectMapper.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// disable to serialize dates for millis timestamps
defaultObjectMapper.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
// using Java8 date time classes
defaultObjectMapper.registerModule(new JavaTimeModule());
}
#Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
}
And that's it

Jackson Custom String to Date Serializer

I am trying to make Jackson to parse String to Date with given format. I came up with the following code for now:
#JsonIgnoreProperties(ignoreUnknown = true)
public class EventData implements Serializable {
private transient SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
#JsonSerialize(using = StringToDateSerializer.class, as = Date.class)
private Date eventStart;
public class StringToDateSerializer extends JsonSerializer<String> {
#Override
public void serialize(String tmpString,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {
try {
jsonGenerator.writeObject(formatter.parse(tmpString));
} catch (ParseException e) {
}
}
}
}
But my only field eventStart always get populated with NULL. Also, it does not stops on breakpoint inside try/catch block as if it is never been invoked. The other (non custom) fields are populated fine.
What I am doing wrong? Thanks
If you are trying to convert JSON into EventData where one of the fields is a data string then you need to deserialize it (serialization is process when you convert your Java object into the JSON/stream of bytes).
Jackson provide better way how to handle Date format:
http://wiki.fasterxml.com/JacksonFAQDateHandling
Since Jackson 2.0 you can use JsonFormat where you can specify custom date format
public class DateStuff {
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd,HH:00", timezone="CET")
public Date creationTime;
}

Setter for LocalDateTime (Java 8 API) gets called twice

I've implemented a class for a Java Web App I'm working on. The class has a LocalDateTime property 'created'. However, when I try to set that property (once), its setter is somehow called twice in succession - first setting the value I want, then setting it to null on a second call that should not even happen.
I've traced through the following method and everything looks well up to the third line.
public static ICEDocument mapDocumentFromSOLR(SolrDocument document) {
ICEDocument result = new ICEDocument();
Date uploaded = (Date) document.getFieldValue("CREATED");
LocalDateTime uploadDate = LocalDateUtils.convertUtcDateToLocalDateTime(uploaded); // custom class
result.setCreated(uploadDate); // **faulty line**
}
Here's the class, shortened for clarity:
import java.time.LocalDateTime;
import org.springframework.data.annotation.Transient;
[...]
#JsonIgnoreProperties(ignoreUnknown=true)
public class ICEDocument implements java.io.Serializable {
[...]
#Transient
private LocalDateTime created;
[...]
#JsonDeserialize(using=LocalDateTimeJsonDeserializer.class)
public void setCreated(LocalDateTime created) {
System.out.println("Setting creation date " + created); // added for debugging
this.created = created;
}
}
Steps I've taken trying to resolve this
Removing the #Transient. The data is filled in via Hibernate (ver5.1), and I originally annotated the property since the field itself is not in the corresponding database table. I thought that might be the problem (see Object Serialization and Java Transient Variables), but removing it didn't change anything.
Changing the third line. I switched it with what was basically inside the static LocalDateUtils method. This didn't resolve the issue.
LocalDateTime uploadDate = uploaded.toInstant().atZone(ZoneId.of("UTC")).toLocalDateTime();
Removing the JSON Deserializer. I don't think the JsonDeserializer is at fault since it isn't supposed to (and doesn't accd. to Debug) do anything at this point, but I'll add it here for completeness sake. Could be I'm just grasping at straws at this point.
public class LocalDateTimeJsonDeserializer extends JsonDeserializer<LocalDateTime> {
private static final String DATE_TIME = "yyyy-MM-dd' 'HH:mm:ss";
#Override
public LocalDateTime deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME);
LocalDateTime deserializedDate = LocalDateTime.parse(parser.getText(), formatter);
return deserializedDate;
}
}
Thank you for reading to the end of my rather long post.
After debugging the code I found a line further down that set the property to null. So it was in fact a second call to the setter and a lot of bad luck, I suppose.
But it might help to know that there wasn't anything wrong with the other factors, so I"ll just leave this here. Thanks again.

Mapping Java Date Object to XML Schema datetime format

I am having some problem mapping my Java Data Type to standard Schema Date data type.
I have a simple class that I annotated like this. The period instance variable is of Java Date object type.
#XmlAccessorType(value = XmlAccessType.NONE)
public class Chart {
#XmlElement
private double amount;
#XmlElement
private double amountDue;
#XmlElement
private Date period;
//constructor getters and setters
}
Here is my Web Service
#WebService
public class ChartFacade {
#WebMethod
public Chart getChart() throws ParseException {
SimpleDateFormat df = new SimpleDateFormat("yyyy-mm-dd");
Chart chart = new Chart(20.0,20.5, df.parse("2001-01-01"));
return chart;
}
}
My problem is it returns the date data in a format not according to what I am expecting.
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:getChartResponse xmlns:ns2="http://ss.ugbu.oracle.com/">
<return>
<amount>20.0</amount>
<amountDue>20.5</amountDue>
**<period>2001-01-01T00:01:00+08:00</period>**
</return>
</ns2:getChartResponse>
</S:Body>
</S:Envelope>
I wanted the period element to be returned like this
<period>2001-01-01</period>
Is there any way I can achieve this?
You can do the following to control the schema type:
#XmlElement
#XmlSchemaType(name="date")
private Date period;
For More Information:
http://bdoughan.blogspot.com/2011/01/jaxb-and-datetime-properties.html
Use #XmlJavaTypeAdapter annotation and you can marshal/unmarshal your fields any way you want.
Cannot tell though if it's the simplest way.
And note also that it may harm interoperability with any code that would try to use your WSDL. The programmers for that other code would see xsd:string as the field type, and therefore will have to do formatting and parsing manually (just like you do, yes), introducing who knows how many bugs. So please consider if the xsd:date a bad choice really.
Stolen from here:
#XmlJavaTypeAdapter(value=DateAdapter.class, type=Date.class)
Date someDate;
...
public class DateAdapter extends XmlAdapter<String, Date> {
// the desired format
private String pattern = "MM/dd/yyyy";
public String marshal(Date date) throws Exception {
return new SimpleDateFormat(pattern).format(date);
}
public Date unmarshal(String dateString) throws Exception {
return new SimpleDateFormat(pattern).parse(dateString);
}
}
UPDATE: as was mentioned by #Blaise Doughan, a much shorter way is to annotate the date with
#XmlSchemaType("date")
Date someDate;
Despite it is still not clear why timezone information is not generated for the date, this code works in practice and requires much less typing.
Your Chart constructor seems to be parsing the formatted date string back into a Date, which is then being serialized using the default format to the XML response.
I guess using private String period; (and fixing the constructors) should work

Categories