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'}
Related
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.
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"
}
I have the following POJO which I use to send out as messages to rabbitmq:
public class MyMessage {
private String id;
private String name;
private Date createdDt;
#JsonCreator
public MyMessage(
#JsonProperty("id") String id,
#JsonProperty("name") String name,
#JsonProperty("createdDt") Date createdDt
) {
this.id = id;
this.name = name;
this.createdDt = createdDt;
}
}
The problem with this is that when I send it using rabbitTemplate.convertAndSend(), the createdDt will be in unix timestamp in the JSON message. I need the createdDt in the JSON after serialised to be in the format of dd-MM-yyyy HH:mm:ss.
I don't want to change the createdDt property in MyMessage class to be a string in that formatted date because I may want to use the POJO else where in the code and having the date as a string is not convenient later. It also doesn't sound "right" to have the date in string just for the purpose of having it in a particular format.
When I'm receiving the message back, I also need to deserialise that string date in the format of dd-MM-yyyy HH:mm:ss back into a Date object.
How can I keep the createdDt as a Date object while sending the date in a different format when serialised and then have the string deserialised back as a date object?
If you must use java.util.Date then just add the following annotation onto the createdDt field
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy HH:mm:ss")
private Date createdDt;
I recommend not using java.util.Date but preferring the Java 8+ Time API. In that case you can import Jackson's built-in support via module com.fasterxml.jackson.datatype:jackson-datatype-jsr310 and ...
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
... will, by default, render LocalDateTime as an ISO 8601 string.
Ref: https://fasterxml.github.io/jackson-datatype-jsr310/javadoc/2.7/com/fasterxml/jackson/datatype/jsr310/JavaTimeModule.html
With that specific requirements regarding serialzation/deserialization of the field I would suggest using custom serializer/deserializer.
public class CustomDateSerializer extends StdSerializer<Date> {
#Override
public void serialize(Date value, JsonGenerator generator, SerializerProvider provider)
throws IOException, JsonProcessingException {
// your implementation
}
}
public class CustomDateDeserializer extends StdDeserializer<Item> {
#Override
public Date deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// your implementation
}
}
Then you can simply mark createdDt like this:
public class MyMessage {
private String id;
private String name;
private Date createdDt;
#JsonCreator
public MyMessage(
#JsonProperty("id") String id,
#JsonProperty("name") String name,
#JsonProperty("createdDt") #JsonDeserialize(using = CustomDateDeserializer.class) #JsonSerialize(using = CustomDateSerializer.class) Date createdDt
) {
this.id = id;
this.name = name;
this.createdDt = createdDt;
}
}
This way you instruct Jackson to use your specific serializer/deserializer on a specific field.
If you would like to make the configuration to be applied on ObjectMapper level you can achieve it with module registration like that:
SimpleModule myModule = new SimpleModule();
myModule.addSerializer(Date.class, new CustomDateSerializer());
myModule.addDeserializer(Date.class, new CustomDateDeserializer());
objectMapper.registerModule(myModule);
I am using Spring Boot + Spring data Redis example to save Date into the Redis Cache. Although I used #DateTimeFormat #JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd"), but still persistance happening is long value. Look like its a millisecond.
Can somebody guide if I need to set extra configurations to persist date like yyyy-MM-dd.
HGETALL users:1
1) "_class"
2) "com.XXX.entity.User"
3) "userId"
4) "1"
5) "name"
6) "John"
7) "createdDate"
8) "1542043247352"
Entity classes:
#Builder
#Data
#AllArgsConstructor
#NoArgsConstructor
#RedisHash("users")
public class User {
#Id
private Long userId;
private String name;
#DateTimeFormat
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date createdDate;
private List<Group> groups;
}
UPDATE-1:: As per suggestion I implemented, but still not working
CustomDateSerializer.java
#Component
public class CustomDateSerializer extends JsonSerializer<Date> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy");
#Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
throws IOException, JsonProcessingException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}
Custom Interface
#Retention(RetentionPolicy.RUNTIME)
public #interface MyJsonFormat {
String value();
}
Model class
#MyJsonFormat("dd.MM.yyyy")
#JsonSerialize(using = CustomDateSerializer.class)
private Date createdDate;
I'd advise using LocalDateTime (or LocalDate if you prefer) instead. You can then annotate your fields with
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime createdAt;
using jackson's jsr310 add on:
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
By Using Custom Serializer, this can be solved. Ref #https://kodejava.org/how-to-format-localdate-object-using-jackson/#comment-2027
public class LocalDateSerializer extends StdSerializer<LocalDate> {
private static final long serialVersionUID = 1L;
public LocalDateSerializer() {
super(LocalDate.class);
}
#Override
public void serialize(LocalDate value, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeString(value.format(DateTimeFormatter.ISO_LOCAL_DATE));
}
}
POJO:
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
private LocalDate createdDate;
I have a class like this:
#Data
#NoArgsConstructor(force = true)
#AllArgsConstructor(staticName = "of")
public class BusinessPeriodDTO {
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
LocalDate startDate;
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
LocalDate endDate;
}
And I used this class inside another class, let's call it PurchaseOrder
#Entity
#Data
#NoArgsConstructor(access = AccessLevel.PROTECTED, force = true)
public class PurchaseOrder {
#EmbeddedId
PurchaseOrderID id;
#Embedded
BusinessPeriod rentalPeriod;
public static PurchaseOrder of(PurchaseOrderID id, BusinessPeriod period) {
PurchaseOrder po = new PurchaseOrder();
po.id = id;
po.rentalPeriod = period;
return po;
}
And I'm trying to populate a purchaseOrder record using jakson and this JSON:
{
"_class": "com.rentit.sales.domain.model.PurchaseOrder",
"id": 1,
"rentalPeriod": {
"startDate": "2016-10-10",
"endDate": "2016-12-12"
}
}
But I have faced with an error:
java.lang.RuntimeException: com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class java.time.LocalDate] from String value ('2016-10-10');
I am sure jakson and popularization works correctly.
Include in your pom.xml:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.6</version>
</dependency>
Then in your BusinessPeriodDTO.java import LocalDateDeserializer as follows:
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
And finally, always in your BusinessPeriodDTO.java file, annotate the interested dates like this:
#JsonDeserialize(using = LocalDateDeserializer.class)
LocalDate startDate;
#JsonDeserialize(using = LocalDateDeserializer.class)
LocalDate endDate;
Old question but I recently had to answer it for myself.
There are different solutions (as commented by rapasoft, see for example here).
The quick solution I used involves adding a setDate(String) method for deserialization.
It might not be the prettiest solution, but it works without updating other classes.
Below a runnable class to demonstrate:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
/**
* Demonstrate Java 8 date/time (de)serialization for JSON with Jackson databind.
* Requires {#code com.fasterxml.jackson.core:jackson-databind:2.8.5}
* and {#code com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.8.5}
*/
public class JdateDto {
/** The pattern as specified by {#link java.text.SimpleDateFormat} */
public static final String ISO_LOCAL_DATE_PATTERN = "yyyy-MM-dd";
/* Used when serializing isoLocalDate. */
#JsonFormat(shape = Shape.STRING, pattern = ISO_LOCAL_DATE_PATTERN)
private LocalDate isoLocalDate;
public LocalDate getIsoLocalDate() {
return isoLocalDate;
}
/* Used when deserializing isoLocalDate. */
public void setIsoLocalDate(String date) {
setIsoLocalDate(LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE));
}
public void setIsoLocalDate(LocalDate isoDate) {
this.isoLocalDate = isoDate;
}
public static void main(String[] args) {
try {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
JdateDto dto = new JdateDto();
dto.setIsoLocalDate(LocalDate.now());
String json = mapper.writeValueAsString(dto);
System.out.println(json);
JdateDto dto2 = mapper.readValue(json, JdateDto.class);
if (dto.getIsoLocalDate().equals(dto2.getIsoLocalDate())) {
System.out.println("Dates match.");
} else {
System.out.println("Dates do not match!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}