Hi I am seeking for some help,
previously I used to get my datetime field as 2022-06-30T22:39:22.235+0000 format but after adding one internal library(Org level) datetime fields are coming as 1656628762235 in json response.
I have tried to add #JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSXXX") in my dto level but no luck also I tried adding below properties in yaml files.
spring.jackson.serialization.write-dates-as-timestamps: true
jackson-datatype-jsr310 version is : 2.9.10
jackson-databind : 2.9.10
spring-boot-2.1.11.RELEASE
please suggest what other option I can try to get the timestamp as in iso format.
#Setter
#Getter
public class TestRequestFormDto implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private UUID trfId;
private String testType;
private ItemDto item;
private List<RepItemsMappingDto> repItems;
private AdditionalQuestionsDto additionalQuestions;
private String hasRepItems;
private TestRequestInfoDto testRequestInfo;
private ItemInformationDto itemInformation;
private LabDto lab;
private Timestamp createdDate;
private String createdBy;
private String status;
private Timestamp modifiedDate;
private String modifiedBy;
private String pageLeftFrom;
private String referenceNumber;
private TrfVendorDto trfVendor;
private TrfFactoryDto trfFactory;
private String originCountry;
private String itemsVbu;
private String versionNumber;
}
previous date format:
{"createdDate": "2022-06-30T22:39:22.235+0000"}
current date format:
{"createdDate": 1656628762235}
Use modern Time API
java.util.Date and its subclasses are legacy.
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.
java.sql.Timestamp as well as Date is obsolete and discouraged to be used.
You're advised to use java.time.Instant instead (if you have the ability to change the types in your DTO).
Seconds to Instant
The task would be simple if your timestamp were represented in seconds (unfortunately it's not, but for completeness I'll describe this option).
In this case, you would need to have a Bean of type JavaTimeModule in the Spring Contexts. Spring-Boot will do the rest for you. JacksonAutoConfiguration will grab the module and register it automatically.
#Configuration
public class JsonConfig {
#Bean
public JavaTimeModule javaTimeModule() {
return new JavaTimeModule();
}
}
Note: if the target field is of type Instant no extra annotations required.
Milliseconds to Instant
In case if your timestamp represented in milliseconds and autoconfiguration would not work (you can try it, the parsed date would be very far in the future).
To resolve this problem, we need to configure ObjectMapper manually by registering JavaTimeModule module and instructing mapper about the precision of the timestamp. For that, would need to place two Beans Jackson2ObjectMapperBuilder and ObjectMapper in the Spring's Context.
#Configuration
public class JsonConfig {
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
return new Jackson2ObjectMapperBuilder();
}
#Bean
public ObjectMapper objectMapper() {
return jackson2ObjectMapperBuilder()
.build()
.registerModule(new JavaTimeModule())
.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
}
}
And as I've said before, no additional steps and no extra annotations on fields required (unless the property name doesn't match).
Usage example:
Input JSON:
{"timestamp": 1656628762235}
Output:
[2022,6,30,22,39,22,235000000]
If you can't a change the data type
As the last resort if you're forced to use legacy date-time representations, here's a couple of possible solutions:
You can declare all-args constructor in your POJO and annotate each argument with #JsonProperty. The trick is to declare the corresponding arguments of type long and parse milliseconds to java.sql.Timestamp manually. But judging by the number of field you have, this approach doesn't look like the best option.
Another option would be to implement a custom Deserializer and annotate the fields of type Timestamp with #JsonDeserialize annotation.
Related
I using RestController in Spring Boot and I have problem with Jackson auto converting time zone. When I try to send date to RestController, the date is auto convert to my current time zone and I don't want to prevent it to happened.
If I send
"NGAY": "2022-07-29T17:00:00.000+00:00",
I want to receive
2022-07-29
no matter what the time zone is
Not
2022-07-30
This is code in model class
#Column(name = "NGAY")
private java.util.Date ngay;
And this is code in my RestController
#RestController
#RequestMapping("/api/sanluong/ngay")
public class Controller {
#PostMapping("/savedata")
public ResponseEntity<?> uploadFile(#RequestBody List<MLoadprofileView> lstData) {
ResponseData result = sanLuongNgayService.onSaveData(lstData);
return ResponseEntity.ok(result);
}
}
I tried in application.properties but it has no effect
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
spring.jackson.deserialization.ADJUST_DATES_TO_CONTEXT_TIME_ZONE = false
EDIT: I want to applied to all java.ulti.Date field in all class
We are currently using SpringBoot and PostgreSQL and we have problem with date format.
When we save or edit(sending POST request from front-end) something we need a YYYY-MM-DD format since any other type of format wont save anything to database so #JSONformat on Entity or some type of annotation like that is not possible. But when we are fetching example all users it would be nicer that we get DD-MM-YYYY in a response from server, and im not sure how to do that.
We can do it from front-end but code isn't nice so back-end solution would be better.
Thanks in advance!
You can create a mapper and map your entity object to a DTO that will be sent to your front-end.
Since you don't specify the class you are using for your dates I will use LocalDate as an example, but the same logic can be applied to your date class.
Let's say your entity class looks like that:
public class SampleEntity {
private LocalDate localDate;
}
Your DTO class will look like that:
public class SampleDto {
// the Jackson annotation to format your date according to your needs.
#DateTimeFormat(pattern = "DD-MM-YYYY")
private LocalDate localDate;
}
Then you need to create the mapper to map from SampleEntity to SampleDto and vice versa.
#Component
public class SampleMapper {
public SampleDto mapTo(final SampleEntity sampleEntity){
// do the mapping
SampleDto sampleDto = new SampleDto();
sampleDto.setLocalDate(sampleEntity.getLocalDate());
return sampleDto;
}
public SampleEntity mapFrom(final SampleDto sampleDto){
// do the mapping
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setLocalDate(sampleDto.getLocalDate());
return sampleEntity;
}
}
You can use all those in your controller like that:
#GetMapping
public ResponseEntity<SampleDto> exampleMethod() {
// service call to fetch your entity
SampleEntity sampleEntity = new SampleEntity(); // lets say this is the fetched entity
sampleEntity.setLocalDate(LocalDate.now());
SampleDto sampleDto = sampleMapper.mapTo(sampleEntity);
return ResponseEntity.ok(sampleDto);
}
With this solution, you can avoid adding Jackson annotations to your entity. Also with this solution, you can control exactly what your front-end can access. More on that here What is the use of DTO instead of Entity?
Having something like:
#Getter #Setter
public static class Entity {
private int hash;
private LocalDateTime createdTime;
}
and
#Getter #Setter
public static class DTO {
private String hash;
private String createdTime;
}
I need birectional mapping so I should be able to map Entity -> DTO -> Entity. In this example the property type happens to be LocalDateTime but could be any type that needs parsing from String or so (just to say that I am not after better way to map LocalDateTime but in general).
There are no problems in mapping. I create TypeMap, add Converter and for LocalDateTime a Provider also since it does note have public default constructor. Something like here.
If I had in my DTO also LocalDateTime createdTime(or String createdTime in my Entity) then ModelMapper.validate() would be happy. But I do not have and I need to create all the converting stuff.
All this leads to ModelMapper.validate() to complain:
Unmapped destination properties found in TypeMap[DTO -> Entity]:
org.example.test.modelmapper.validation.TestIt$Entity.setCreatedTime()
The code I currently use for validating mapping for LocalDateTime case is:
ModelMapper mm = new ModelMapper();
mm.createTypeMap(Entity.class, DTO.class);
mm.createTypeMap(DTO.class, Entity.class);
mm.createTypeMap(String.class, LocalDateTime.class)
.setPropertyProvider(localDateTimeProvider);
mm.addConverter(toStringDate);
mm.validate();
(so I am not doing any actual mapping but validating the mapping)
with
Provider<LocalDateTime> localDateTimeProvider =
new AbstractProvider<LocalDateTime>() {
#Override
public LocalDateTime get() {
return LocalDateTime.now();
}
};
and
Converter<String, LocalDateTime> toStringDate = new AbstractConverter<>() {
#Override
protected LocalDateTime convert(String source) {
return LocalDateTime.parse(source);
}
};
Ask for more details/code. I'll update question as needed
The setPropertyProvider method allows to specify a Provider to be used for providing instances of mapped properties within a TypeMap.
So when you write:
mm.createTypeMap(String.class, LocalDateTime.class)
.setPropertyProvider(localDateTimeProvider);
It does not fit the case because we are not using this provider in the mapping of a property of the String type to a property of a LocalDateTime type. It should rather be moved above to be associated with the DTO -> Entity TypeMap (The error message is by the way a good hint about that). So it should rather be.
mm.createTypeMap(DTO.class, Entity.class)
.setPropertyProvider(localDateTimeProvider);
Which makes perfect sense because we are using the provider to provide instance for the mapping of a String property of the DTO (String createdTime;) to a LocalDateTime property of the Entity (LocalDateTime createdTime;).
On the other hand the converter should be added to the ModelMapper before the corresponding provider.
Also leaving in mm.createTypeMap(String.class, LocalDateTime.class), my compiler complains that a similar typemap already exist and there is no need to create a new one. So with that I can discard it.
With these two changes, my bean looks like:
#Bean
ModelMapper demoModelMapper() {
Provider<LocalDateTime> localDateTimeProvider =
new AbstractProvider<LocalDateTime>() {
#Override
public LocalDateTime get() {
return LocalDateTime.now();
}
};
Converter<String, LocalDateTime> toStringDate = new AbstractConverter<String,
LocalDateTime>() {
#Override
protected LocalDateTime convert(String source) {
return LocalDateTime.parse(source);
}
};
ModelMapper mm = new ModelMapper();
mm.createTypeMap(Entity.class, DTO.class);
mm.addConverter(toStringDate);
mm.createTypeMap(DTO.class, Entity.class)
.setPropertyProvider(localDateTimeProvider);
mm.validate();
return mm;
}
Notice that I am calling validate() before returning the bean. This works for me. Please test and see on your side.
As in answer from alainlompo I had to move the adding of converter before the creation of type map.
But I also had to remove the provider part because it seemed to cause all string fields to be mapped as LocalDateTime so I got errors like:
org.modelmapper.MappingException: ModelMapper mapping errors:
1) The provided destination instance 2020-01-05T17:28:22.088694 is not of the required type int.
Above I think means that ModelMapper tried to populate field hash with a string representing LocalDateTime.
It seems that the provider is not needed at all. So my final code with just a converter added:
ModelMapper mm = new ModelMapper();
mm.createTypeMap(Entity.class, DTO.class);
mm.addConverter(toStringDate);
mm.createTypeMap(DTO.class, Entity.class);//.setPropertyProvider(localDateTimeProvider);
mm.validate();
This actually means that I asked a bit wrong question claiming that I need to use the provider
In a spring project, I'd like to create a LocalDate from an #Autowired constructor parameter whose value is in a .properties file. Two things I'd like to do:
1. If the property file contains the property my.date, the parameter should be created by parsing the property value
When the property is set, and when I use the following:
#DateTimeFormat(pattern = "yyyy-MM-dd") #Value("${my.date}") LocalDate myDate,
...
I get this error:
java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.time.LocalDate': no matching editors or conversion strategy found
I have also used the iso = ... to use an ISO date with the same result.
2. If the property is not in the properties file, the parameter should be created using LocalDate.now()
I tried using a default value as such:
#Value("${my.date:#{T(java.time.LocalDate).now()}}") LocalDate myDate,
...
But I get the same error.
Forgive my ignorance with Spring, but how can I achieve the two objectives here?
I know two ways. One is generic for any object - to use #Value annotation on custom setter
#Component
public class Example {
private LocalDate localDate;
#Value("${property.name}")
private void setLocalDate(String localDateStr) {
if (localDateStr != null && !localDateStr.isEmpty()) {
localDate = LocalDate.parse(localDateStr);
}
}
}
The second is for LocalDate/LocalDateTime
public class Example {
#Value("#{T(java.time.LocalDate).parse('${property.name}')}")
private LocalDate localDate;
}
Sample property:
property.name=2018-06-20
Spring Boot 2.5, works perfect:
application.yaml
my.date: 2021-08-14
my.time: "11:00"
#Service
public class TestService {
#Value("${my.date}")
#DateTimeFormat(pattern = "yyyy-MM-dd")
LocalDate myDate;
#Value("${my.time}")
#DateTimeFormat(iso = DateTimeFormat.ISO.TIME)
LocalTime myTime;
}
If you want to specify the date format as well then use following on the field:
#Value("#{T(java.time.LocalDate).parse('${date.from.properties.file}', T(java.time.format.DateTimeFormatter).ofPattern('${date.format.from.properties.file}'))}")
Try to add this into your properties file:
spring.jackson.date-format=com.fasterxml.jackson.databind.util.ISO8601DateFormat
spring.jackson.time-zone=UTC
and remove #DateTimeFormat annotation
Concerning LocalDate.now() initialization. Try to use field injection this way:
#Value("${my.date}") LocalDate myDate = LocalDate.now();
As mentioned in other answer by Pavel there are two ways.
I am providing similar two ways with modification to handle 2nd Point by OP.
If the property is not in the properties file, the parameter should be
created using LocalDate.now()
#Component
public class Example {
private LocalDate localDate;
#Value("${property.name}")
private void setLocalDate(String localDateStr) {
if (localDateStr != null && !localDateStr.isEmpty()) {
localDate = LocalDate.parse(localDateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}else{
localDate = LocalDate.now();
}
}
}
I Prefere 2nd way though...
public class Example {
#Value("#{T(java.time.LocalDate).parse('${property.name}', T(java.time.format.DateTimeFormatter).ofPattern('yyyy-MM-dd')) ?: T(java.time.LocalDate).now()}")
private LocalDate localDate;
}
Edit:- Fixed 2nd Way
#Value("#{ !('${date:}'.equals('')) ? T(java.time.LocalDate).parse('${date:}', T(java.time.format.DateTimeFormatter).ofPattern('MM-dd-yyyy')) " +
":T(java.time.LocalDate).now()}")
private LocalDate asOfDate;
For the first bit, you could create a converter:
#Component
#ConfigurationPropertiesBinding
public class LocalDateConverter implements Converter<String, LocalDate> {
#Override
public LocalDate convert(String s) {
if (s == null) {
return null;
}
return LocalDate.parse(s);
}
}
Your config Class will automatically use this for conversion.
For the 2nd you can just do:
if(my.date == null) iso = LocalDate.now()
There's an example of initializing a LocalDateTime value using annotations and configuration properties. I've tested that it works in Spring Boot 2.4.
MyComponent.kt fragment:
#Value("\${my.date}")
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
val myDate: LocalDateTime
application.yml:
my:
date: "2023-01-23T00:00:00"
My problem is with having Spring bind the data I get from a form to a JPA entity. The wierd part is, it works just fine if I do not look at the BindingResults. The BindingResults says there were binding errors when an empty string is passed in for the field graduation, but I know it does bind them correctly because when I don't check Hibernate updates the database perfectly. Is there a way to not have to write logic to circumnavigate the wrongly fired binding errors?
#Entity
#Table(name="child")
public class Child {
#Id
#Column(name="id")
private Integer childId;
#ManyToOne(fetch=FetchType.EAGER )
#JoinColumn(name="house", referencedColumnName="house")
private House house;
#NotNull()
#Past()
#Column(name="birthday")
private Date birthday;
#Column(name="graduation_date")
private Date graduationDay;
}
I have tried the following lines in a property editor to no avail
SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd, yyyy");
registry.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
Here is the method signature for the controller method Handling the request
#Controller
#SessionAttributes(value="child")
#RequestMapping(value="child")
public class ChildModController {
#RequestMapping(value="save-child.do", params="update", method = RequestMethod.POST)
public #ResponseBody Map<String,?> updateChild(
HttpServletRequest request,
#Valid #ModelAttribute(value="child")Child child,
BindingResult results)
}
This is what I get from the BindingResult class as a message
09:01:36.006 [http-thread-pool-28081(5)] INFO simple - Found fieldError: graduationDay,
Failed to convert property value of type java.lang.String to required type java.util.Date for property graduationDay;
nested exception is org.springframework.core.convert.ConversionFailedException:
Failed to convert from type java.lang.String to type #javax.persistence.Column java.util.Date for value ';
nested exception is java.lang.IllegalArgumentException
Spring automatically binds simple object types like String and Number, but for complex objects like java.util.Date or your own defined types, you will need to use what is called a PropertyEditors or Converters, both could solve your problem.
Spring already has a predefiend PropertyEditors and Converters like #NumberFormat and #DateTimeFormat
You can use them directly on your fields like this
public class Child {
#DateTimeFormat(pattern="dd/MM/yyyy")
private Date birthday;
#DateTimeFormat(iso=ISO.DATE)
private Date graduationDay;
#NumberFormat(style = Style.CURRENCY)
private Integer myNumber1;
#NumberFormat(pattern = "###,###")
private Double myNumber2;
}
Spring also allows you to define your own type converters which you must use it combined with Spring ConversionService
For example if you have a Color class like this
public class Color {
private String colorString;
public Color(String color){
this.colorString = color;
}
}
You would define the color converter for example like this
public class StringToColor implements Converter<String, Color> {
public Color convert(String source) {
if(source.equal("red") {
return new Color("red");
}
if(source.equal("green") {
return new Color("green");
}
if(source.equal("blue") {
return new Color("blue");
}
// etc
return null;
}
}
To check more about converters check this, also check this to know the difference between Converters and PropertyEditors