Jackson Custom String to Date Serializer - java

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

Related

Instant incorrectly parsing epoch string with rest api [closed]

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();
}

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

Jackson DateFormatter set timezone without changing time

I have a problem with jackson.
I have a date in the database in the following format :
2018-01-01 13:00:00
I convert the Database entry to a "Date" object.
I now want the jackson objectmapper to print it out date in the following format like :
2018-01-01T13:00:00.000+0200
Now I have the following code where I set up my objectmapper
private static ObjectMapper init() {
ObjectMapper mapper = new ObjectMapper();
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM dd'T'HH:mm:ss.SSSZ");
dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
mapper.setDateFormat(dateFormat);
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
Now the problem is, that the date is printed out like this :
2018-01-01T15:00:00.000+0200
While formatting the date and adding the timezone information, it calculates the new time. All my dates use the "Europe/Berlin" timezone but I don't save it with this information in the database.
Is there any way how to add the timezone information without changing the time?
Regards
The solution was to use a custom DateFormat class, where I can handle the formatting.
Very important :
You have to implement the "clone" method. If you use Eclipse it only shows an error for the missing format and parse method. The clone method was missing and i got some nullpointer exceptions at the DateFormat class.
mapper.setDateFormat(JsonDateFormat);
public class JsonDateFormat extends DateFormat implements Serializable, Cloneable {
#Override
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) {
// TODO DO STUFF
return toAppendTo;
}
#Override
public Date parse(String source, ParsePosition pos) {
// TODO DO STUFF
return null;
}
#Override
public Object clone() {
return new JsonDateFormat();
}
}

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

Deserializing badly formatted dates as null in Jackson

I have a Java web application which uses Jackson 2.x to deserialize JSON requests. I'm currently interfacing with an external application which is sending JSON data with improperly formatted dates in one property of one of the classes. This is causing exceptions with that client's calls (as well it should). However, for business reasons we need a temporary workaround to accept these requests.
Until that client fixes the data it sends to my application (which may be a while), I want to treat any bad dates in that property as null. I do not want to change the actual class itself, as it is a public API which exposed to other clients, and I don't want this temporary workaround included in that class.
Is there an easy way to configure Jackson to treat invalid dates as null? I'm seeing similar functionality in DeserializationFeature, but nothing specifically for this.
A solution that would require you overriding the setter of the original class in a subclass:
public class InvalidDateDeserializer extends JsonDeserializer<Date>
{
#Override
public Date deserialize(JsonParser jsonParser,
DeserializationContext deserializationcontext) throws IOException, JsonProcessingException {
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy'T'HH:mm:ss");
String date = jsonParser.getText();
try {
return sdf.parse(date);
} catch (ParseException e) {
return null;
}
}
}
Afterwards, you would just need to annotate the overriden setter in your class with
#JsonDeserialize(using = InvalidDateDeserializer.class)
So the new DTO class would look like this:
public MyModel extends TheirModel {
#Override
#JsonDeserialize(using = InvalidDateDeserializer.class)
public void setProblematicDate() {
super.setProblematicDate();
}
}
I understand this is no magic flag solution, but it should work in your case without changes to the original DTO class.

Categories