I have a java spring boot rest controller which accepts a java object.
#PutMapping(path = "api/v1/examples/{id}/update")
public ResponseEntity<Void> updateExample(#PathVariable("id") String personsId, #RequestBody ExampleDto dto) {
return ResponseEntity.noContent().build();
}
The ExampleDto looks like this
public class Example {
#ApiModelProperty(example = "2021-02-18T13:45:07+02:00", notes = "Date pattern is yyyy-MM-dd'T'HH:mm:ssZ example value 2021-02-18T13:45:07+02:00")
private ZonedDateTime date;
public String getDate() {
return date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"));
}
public void setDate(String date) {
this.date = ZonedDateTime.parse(date, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
}
This works the way I like it. It generates the swagger file correctly and in the swaggerui It suggests the correct value which my dto can parse with the setDate(String date) method.
But I would like to make a reusable component that I can just add to my api dtos where I wont have to create all the methots.
If I wanted to add multible date fields my code would look like this.
public class Example {
#ApiModelProperty(example = "2021-02-18T13:45:07+02:00", notes = "Date pattern is yyyy-MM-dd'T'HH:mm:ssZ example value 2021-02-18T13:45:07+02:00")
private ZonedDateTime date;
public String getDate() {
return date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"));
}
public void setDate(String date) {
this.date = ZonedDateTime.parse(date, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
#ApiModelProperty(example = "2021-02-18T13:45:07+02:00", notes = "Date pattern is yyyy-MM-dd'T'HH:mm:ssZ example value 2021-02-18T13:45:07+02:00")
private ZonedDateTime dateTwo;
public String getDateTwo() {
return dateTwo.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"));
}
public void setDateTwo(String date) {
this.dateTwo = ZonedDateTime.parse(date, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
}
I would like if I could do something like this.
#Getter
#Setter
public class Example {
private MyDateType date;
private MyDateType dateTwo;
}
I can already do this. But then my json would look like this.
{
"date": {
"innerDate": "2021-02-18T13:45:07+02:00"
},
"dateTwo": {
"innerDate": "2021-02-18T13:45:07+02:00"
},
}
And I want the json to look like this
{
"date": "2021-02-18T13:45:07+02:00",
"dateTwo": "2021-02-18T13:45:07+02:00"
}
Related
I have a JSON Data Object class as follows:
public class Plugins {
private String id;
private String name;
#JsonProperty("created_at")
private long createdAt;
}
Where createdAt is the long timestamp of the creation date. I use this class to back up a Jackson ObjectMapper object parsing JSON data from an external API call. I was wondering if it is possible to have Jackson convert created_at automatically to a readable date format and store in Java as a String or Date flavour?
if it is possible to have Jackson convert created_at automatically to a readable date format and store in Java as a String or Date flavour?
Date is obsolete and discouraged to be used.
Since Java 8 (which was released about 10 years ago) we have modern Time API which includes Instant, LocalDateTime and other classes from the java.time package.
You can change your POJO to make it store date-time information properly without the need to change the JSON payload. I.e. created_at can be received as a long value like 1665148545 and translated into ZonedDateTime (or other date-time representations like Istant, LocalDateTime).
public class Plugins {
private String id;
private String name;
private ZonedDateTime createdAt;
public Plugins(#JsonProperty("id") String id,
#JsonProperty("name") String name,
#JsonProperty("created_at") long createdAt) {
this.id = id;
this.name = name;
this.createdAt = Instant.ofEpochSecond(createdAt)
.atZone(ZoneId.of("UTC"));
}
// getters, toString(), etc.
}
Usage example:
String json = """
{
"id": "1",
"name": "someName",
"created_at": 1665148545
}""";
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(json, Plugins.class));
Output:
lugins{id='1', name='someName', createdAt=2022-10-07T13:15:45}
You just need to register JavaTimeModule module and use required type from Java-8 time package. Take a look on below example:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.Instant;
public class DateApp {
private final static JsonMapper JSON_MAPPER = JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.addModule(new JavaTimeModule())
.build();
public static void main(String[] args) throws Exception {
String json = "{\"id\": \"1\",\"name\":\"someName\",\"created_at\": 1665148545}";
Plugins plugins = JSON_MAPPER.readValue(json, Plugins.class);
System.out.println(plugins);
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
class Plugins {
private String id;
private String name;
#JsonProperty("created_at")
private Instant createdAt;
}
Above code prints:
Plugins(id=1, name=someName, createdAt=2022-10-07T13:15:45Z)
Using Custom Deserialiser in jackson
You can achieve the date conversion from long to String or Date by using the custom deserialiser. This custom deserialiser will convert the long value from the json into the defined date format(either Date or String).
Please Note: Here, I have converted the epoch value into the String datatype. In case if Date datatype is needed, you can change the implementation of the deserialize method of CustomDateSerializer class accordingly.
You need to use the below annotation to the fields on which custom deserialisation is required.
#JsonDeserialize(using = CustomDateSerializer.class)
Please find the code below:
Plugins.java
public class Plugins {
private String id;
private String name;
#JsonDeserialize(using = CustomDateSerializer.class)
#JsonProperty("created_at")
private String createdAt;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCreatedAt() {
return createdAt;
}
public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
#Override
public String toString() {
return "Plugins{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", createdAt='" + createdAt + '\'' +
'}';
}
}
CustomDateSerializer.java
public class CustomDateSerializer extends StdDeserializer<String> {
public static String pattern = "dd MMM yyyy hh:mm:ss";
public CustomDateSerializer() {
this(StdDeserializer.class);
}
protected CustomDateSerializer(Class<?> c) {
super(c);
}
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
SimpleDateFormat formatter = new SimpleDateFormat(pattern);
return formatter.format(new Date(jsonParser.getLongValue()));//change the implementation of deserialise method if date format is needed.
}
}
Test.java
public class Test {
public static void main(String[] args) throws JsonProcessingException {
//For sample input json, here i have used Text Blocks feature available from JDK 15 to have the string in readable format.
String json = """
{
"id":"1",
"name":"test",
"created_at":1665158083000
}
""";
ObjectMapper mapper = new ObjectMapper();
Plugins test = mapper.readValue(json,Plugins.class);
System.out.println(test);
}
}
Output:
Plugins{id='1', name='test', createdAt='07 Oct 2022 09:24:43'}
I have a Mongo collection with objects of this format:
{
id: 1,
date: "2020-08-06T12:00:00Z",
...
}
I have Java code that needs to read from this collection but never writes to it. The process that writes to this collection is not owned by me so I can't necessarily change the format of that date string. I initially tried to model my Java Morphia object like this:
#Entity public class MyDocument {
#Id
private Integer id;
private Date date;
...
}
This did not work because Morphia didn't know how to deserialize that date format into a Date object. The solution that I came up with was treating the date as a String on the POJO and then having a getDate() method that did the actual deserialization. I am wondering, is there a better way for me to do this? I know if you're using Jackson you can annotate certain fields with #JsonDeserialize and pass a deserializer so I was wondering if there was something similar for Morphia.
My solution (which feels suboptimal to me):
#Entity public class MyDocument {
#Id
private Integer id;
private String date;
...
private Date getDate() {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
try {
return dateFormat.parse(date);
} catch (Exception ex) {
return null;
}
}
}
You can go ahead and create a simple converter extending the TypeConverter like so:
public class DateConverter extends TypeConverter {
private static final String FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
private final SimpleDateFormat simpleDateFormat;
public DateConverter() {
super(Date.class);
this.simpleDateFormat = new SimpleDateFormat(FORMAT);
}
#Override
public Object decode(Class<?> targetClass, Object fromDBObject, MappedField optionalExtraInfo) {
try {
return simpleDateFormat.parse(((String) fromDBObject));
} catch (ParseException e) {
return null;
}
}
}
The go ahead and register your formatter for your document entity like so:
#Entity("Documents")
#Converters(DateConverter.class)
public class Document {
#Id
private Integer id;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Date date;
public Date getDate() { return date; }
public void setDate(Date date) { this.date = date; }
#Override
public String toString() {
return "Document{" +
"id=" + id +
", date=" + date +
'}';
}
}
This will effectively tell Morphia to decode the database incoming values via parsing the string with the desired pattern, resulting directly into a concrete Date object without any additional conversion logic.
In my MongoDB a document looks like this:
{
"_id" : ObjectId("5613700bc00eac21886b6a51"),
"firstname" : "Marc",
"lastname" : "Anonymous",
"email" : "marc.XXXXXX#hotmail.com",
"phone" : "+41/12/345678",
"timestamp" : ISODate("2015-10-06T06:54:03.905+0000"),
"state" : "waiting"
}
I am using gson to parse the json into my Java class User which has a variable timestamp and is a Date but I get the following error:
Caused by: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 179 path $.timestamp
I insert the timestamp into MongoDb via Date too. I don't know how to handle this. Should I just use a String and convert it every time I need in in Date form?
public class User {
public String firstname;
public String lastname;
public String email;
public String phone;
public Date timestamp;
public String state;
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
//...
Edit
I get the json via the MongoDb Java driver. The error occurs when I want to deserialize the String with gson
public ArrayList<User> findUserByEmail(String email) {
Gson g = new Gson();
ArrayList<User> l = new ArrayList<>();
MongoDatabase db = con.getDatabase("waitinglist");
MongoCollection col = db.getCollection("users");
MongoCursor<Document> f = col.find(eq("email", email)).iterator();
while (f.hasNext()) {
Document d = f.next();
System.out.println(d.toJson());
l.add(g.fromJson(d.toJson(), User.class));
}
return l;
}
d.toJson() returns
{
"_id": {
"$oid": "5613700bc00eac21886b6a51"
},
"firstname": "Marc",
"lastname": "xxxx",
"email": "marc.xxxxxx#hotmail.com",
"phone": "+41/12/345678",
"timestamp": {
"$date": 1444114443905
},
"state": "waiting"
}
The error is being thrown because the Date gets convertetd into an own document and when I want to deseriazable it with Gson the Json does not fit with my class.
You can solve this problem by using Jongo and changing your code to the following:
public ArrayList<User> findUserByEmail(String email) {
Gson g = new Gson();
ArrayList<User> l = new ArrayList<>();
Jongo jongo = con.getDB("waitinglist");
Iterator<User> users = jongo.getCollection("users").find("{email: #}", email).as(User.class).iterator();
while(users.hasNext()){
l.add(users.next());
}
return l;
}
Otherwise you're going to have to do something like the following...
Make a wrapper class like this
public static class GsonCompatibleDate {
#SerializedName("$date")
public Long date;
public GsonCompatibleDate(Long date) {
this.date = date;
}
public Date getDate() {
return new Date(date);
}
public void setDate(Date date) {
this.date = date.getTime();
}
}
Note the annotation #SerializedName("$date"). This just lets you use a nicer Java-style name for your variable;
Update your POJO to this or something similar:
public class User {
public String firstname;
public String lastname;
public String email;
public String phone;
public GsonCompatibleDate timestamp;
public String state;
public Date getTimestamp() {
return timestamp.getDate();
}
}
Your de-serialization code would stay exactly as it was before. The updated POJO now matches your Mongo JSON output.
I tested both methods and they work for me. I also highly recommend using a library like Jongo, Morphia, etc... instead of using the Mongo driver itself, unless absolutely necessary.
The timestamp '2015-06-15 13:01:48' which is stored in MySQL database is coming as 1434369708000 in my rest api response. How to handle so that the response also has the same format. I'm using Java, Hibernate, Restful WS with MySQL.
Entity:
private Date CreatedDateTime;
#Column(name = "created_Date_Time", columnDefinition = "TIMESTAMP")
public Date getCreatedDateTime() {
return createdDateTime;
}
public void setCreatedDateTime(Date createdDateTime) {
this.createdDateTime= createdDateTime;
}
JSON View:
#JsonView({MessageView.class})
public Date getCreatedDateTime() {
if (device != null) {
return device.getCreatedDateTime();
}
return null;
}
public void setCreatedDateTime(Date CurrentServerUTC) {
if (device != null) {
this.device.getCreatedDateTime(CurrentServerUTC);
}
}
http://fasterxml.github.io/jackson-annotations/javadoc/2.0.0/com/fasterxml/jackson/annotation/JsonFormat.html
On Entity try annotating CreatedDateTime with:
#JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss")
private Date CreatedDateTime;
Double check the pattern though, I'm not %100 sure it's correct. It's the same as Java SimpleDatePattern string.
PS: non-static field names start with lowercase letter in Java as a convention.
I wrote a simple test to showcase this annotation:
public class JacksonDateTest {
#Test
public void test() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectMapper om = new ObjectMapper();
om.writeValue(baos, new Sth());
baos.write("\n Sth2 \n".getBytes());
om.writeValue(baos, new Sth2());
System.out.println(baos.toString());
baos.close();
}
public class Sth {
#JsonFormat(pattern = "yyyy-MM-dd kk:mm:ss")
Date creationDate = new Date();
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
}
public class Sth2 {
Date creationDate = new Date();
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
}
}
It works. Sth is serialized as:
{"creationDate":"2015-06-16 16:09:06"}
while Sth2 is serialized as:
{"creationDate":1434470946137}
There must be something else going wrong in your code.
I have a model class with a persisted DateTime field that is only interacted via getters/setters for Transient properties, String date; and String time;. The do some very specific formatting to create the DateTime object that will be persisted or retrieved when need be.
The problem is that when my model's loaded from the yaml file, the setter for the time field receives a String value that doesn't correspond at all to anything in my project/code.
Here's the class with only relevant members:
package models;
import javax.persistence.*;
import org.hibernate.annotations.*;
import org.joda.time.*;
import org.joda.time.format.*;
import play.db.jpa.*;
#javax.persistence.Entity
public class Booking extends Model {
#Column
#Type(type = "org.joda.time.contrib.hibernate.PersistentDateTime")
public DateTime datetime;
public Integer duration;
#Transient
public String date;
#Transient
public String time;
//default constructor called by play's model loader that sets default values that are required for the getters and setters to work.
public Booking() {
DateTimeFormatter fmt = DateTimeFormat.forPattern("'ISO8601':yyyy-MM-dd'T'HH:mm:ssZ");
this.datetime = fmt.parseDateTime("ISO8601:1970-01-01T00:00:00+0200");
//this.datetime = fmt.parseDateTime(this.date+"T"+this.time);
}
public void setDate(String dateStr) {
this.date = dateStr;
if (dateStr.contains("ISO")) {
DateTimeFormatter dt = DateTimeFormat.forPattern("'ISO8601':yyyy-MM-dd'T'HH:mm:ssZ");
DateTime tmp = dt.parseDateTime(dateStr);
this.datetime = toDateTime(tmp.toString("yyyy-MM-dd"), getTime());
} else {
this.datetime = toDateTime(dateStr, getTime());
}
}
public void setTime(String timeStr) {
this.time = timeStr; //timeStr = "780" for some reason?!
if (timeStr.contains("ISO")) {
DateTimeFormatter dt = DateTimeFormat.forPattern("'ISO8601':yyyy-MM-dd'T'HH:mm:ssZ");
DateTime tmp = dt.parseDateTime(timeStr);
this.datetime = toDateTime(getDate(), tmp.toString("HH:mm"));
}
this.datetime = toDateTime(getDate(), timeStr);
}
public String getDate() {
DateTimeFormatter format = DateTimeFormat.forPattern("yyyy-MM-dd");
return this.datetime.toString(format);
}
public String getTime() {
DateTimeFormatter format = DateTimeFormat.forPattern("HH:mm");
return this.datetime.toString(format);
}
private DateTime toDateTime(String dateStr, String timeStr) {
DateTimeFormatter fmt = ISODateTimeFormat.dateHourMinute();
DateTime dt = fmt.parseDateTime(dateStr + "T" + timeStr);
return dt;
}
When I run through the debugger, the timeStr parameter that setTime receives when it's first called is "780". There is no such value in my yaml file as the model is injected like this:
Booking(bobBooking):
date: 2011-09-16
time: 13:00
duration: 30
headcount: 10
room: b
user: bob
description: Bob's Booking.
The additional fields are omitted.
Try using quotes for time value in yaml file. There could be some issue in parsing colon fields using SnakeYAML parser (which is the default in Play)
YAML 1.1 defines 13:00 as a sexagesimal value (which is not what you expect)
http://yaml.org/type/int.html
Use single or double quotes to specify a string value. ('13:00', "13:00")