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;
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 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);
When I'm trying to test my Api Controller with hardcoded Object everything is fine unitil I try to add LocalDate parameter to Object.
My Test:
#RunWith(SpringRunner.class)
#WebMvcTest(ApiTransitController.class)
public class ApiTransitControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private TestService testService;
#MockBean
private ReportsService reportsService;
#MockBean
private TransitService transitService;
#Test
public void shouldCreateTransit() throws Exception {
Transit transit = new Transit("London", "Paris", 12L,
LocalDate.of(2018,10,12));
ObjectMapper objectMapper = new ObjectMapper();
String transitJsonString = objectMapper.writeValueAsString(transit);
this.mockMvc.perform(post("/api/transit")
.contentType(MediaType.APPLICATION_JSON)
.content(transitJsonString))
.andExpect(status().isCreated());
verify(transitService).addTransit(eq(new Transit("London", "Paris", 12L,
LocalDate.of(2018,10,12))));
}
}
Model:
#Entity
public class Transit {
#Column(name = "id")
#Id
#GeneratedValue
private Long id;
private String sourceAdress;
private String destinationAdress;
private Long price;
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private LocalDate date;
#JsonSerialize(using=DistanceSerializer.class)
private Long distance;
public Transit(String sourceAdress, String destinationAdress, Long price, LocalDate date) {
this.sourceAdress = sourceAdress;
this.destinationAdress = destinationAdress;
this.price = price;
this.date = date;
}
//getters and setters, equals and hashCode and toString
Api Controller:
#PostMapping("/api/transit")
#ResponseStatus(HttpStatus.CREATED)
public void createTransit(#RequestBody Transit transit){
LOG.info("Saving transit={}", transit);
transitService.addTransit(transit);
}
I tried adding DateTimeFormmater and few other ways, but still I cant pass the test. Thank you for your time.
Try changing this line
verify(transitService).addTransit(eq(new Transit("London", "Paris", 12L,
LocalDate.of(2018,10,12))));
to this:
verify(transitService).addTransit(eq(transit));
The two objects aren't equal, also you don't need to create a new object you can use already created one.
I added JsonSerializer to date of the Model:
Model:
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
#JsonSerialize(using=DateSerializerNumberTwo.class)
private LocalDate date;
Serializer:
public class DateSerializerNumberTwo extends StdSerializer<LocalDate> {
private static DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd");
public DateSerializerNumberTwo(){
this(null);
}
protected DateSerializerNumberTwo(Class<LocalDate> t){
super(t);
}
#Override
public void serialize(LocalDate value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeString(formatter.format(value));
}
}
And the test passes without any changes in the test code. I think it's beacuse the Json default response of date is "yyyy, mm, dd", not like Local date (yyyy-mm-dd)".
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.
I've got model and field like this:
#Element(name = "TIMESTAMP")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime date;
In response I received:
<TIMESTAMP>2016-05-04T13:13:42.000</TIMESTAMP>
but during parsing xml to model I have error:
"message": "org.simpleframework.xml.core.PersistenceException: Constructor not matched for class java.time.LocalDateTime",
I also tried with:
#Element(name = "TIMESTAMP")
#DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS")
private LocalDateTime date;
and this still doesn't work. Any Idea ? I am using springframework.xml lib.
The problem is by default simplexml lib doesn't know how to serialize/deserialize new Java8 date types.
In order to succeed you need to use custom converter.
Example entity (see the special #Convert annotation)
public class Entity {
#Element(name = "TIMESTAMP")
#Convert(LocalDateTimeConverter.class)
private LocalDateTime date;
// omitted
}
Special converter
public class LocalDateTimeConverter implements Converter<LocalDateTime> {
public LocalDateTime read(InputNode node) throws Exception {
String name = node.getValue();
return LocalDateTime.parse(name, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
public void write(OutputNode node, LocalDateTime input) {
String value = input.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
node.setValue(value);
}
}
Usage
Strategy strategy = new AnnotationStrategy();
Persister persister = new Persister(strategy);
Entity serializedEntity = persister.read(Entity.class, xmlInputStream);
Full source is available on GitHub