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?
Related
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.
I want to filter out some fields in the response. Filtering should be done before the Java object is serialised into the JSON.
Consider:
public class Entity {
#JsonProperty("some_property")
String someProperty;
#JsonProperty("nested_entity")
#OneToMany(mappedBy = "entity", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
NestedEntity nestedEntity;
// other fields for eg fieldA, fieldB
}
API endpoint
get api/entity/{id}?fields=some_property,field_a
Now the ask is, in the o/p we should filter out only someProperty and fieldA. Like
{
"some_property": "foo",
"field_a": "bar"
}
But since these are JSON fields not Java object fields I can't filter or get these fields them by Reflection. Is there a way we can achieve this, i.e. filtering of Java object based on json fields ?
FYI: The advantage of filtering before serialization is that the lazy-fields' DB calls are saved unless these fields are required
Thanks in advance!
On the suggestion of #robocode using #JsonFilter and also to support empty fields or no fields filtering added JacksonConfiguration
#JsonFilter("entityFilter")
public class Entity {
#JsonProperty("some_property")
String someProperty;
// other fields for eg fieldA, fieldB
}
#Configuration
public class JacksonConfiguration {
public JacksonConfiguration(ObjectMapper objectMapper) {
objectMapper.setFilterProvider(new SimpleFilterProvider().setFailOnUnknownId(false));
}
}
public class FieldMapper {
#SneakyThrows
public static Dto getFilteredFields(Dto make, String fields[]) {
ObjectMapper objectMapper = new ObjectMapper();
if(ArrayUtils.isNotEmpty(fields)) {
FilterProvider filters = new SimpleFilterProvider().addFilter(
"entityFilter", SimpleBeanPropertyFilter.filterOutAllExcept(fields)
);
objectMapper.setFilterProvider(filters);
} else {
objectMapper.setFilterProvider(new SimpleFilterProvider().setFailOnUnknownId(false));
}
JsonNode j = objectMapper.readTree(objectMapper.writeValueAsString(make));
return objectMapper.convertValue(j, Dto.class);
}
}
// controller code to get the dto for api/entity/{id}?fields=some_property,field_a
Dto filteredFields = getFilteredFields(dto, fields);
return filteredFields
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
I am working with a project that is generated with jhipster. It is a micro service architecture project.
In my entity class properties are named with camel case. So when I create a rest service it gives me json, where the json property names are as same as the entity properties.
Entity class
#Entity
#Table(name = "ebook")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Document(indexName = "ebook")
public class Ebook implements Serializable {
private Long id;
private String nameBangla;
private String nameEnglish;
Json response
{
"id": 0,
"nameBangla": "string",
"nameEnglish": "string"
}
I want that my entity property will camel case, But in json response it will snake case. That is I don't want to change my entity class but I want to change my json response like bellow
{
"id": 0,
"name_bangla": "string",
"name_english": "string"
}
You have two possibilities:
Explicit naming your properties:
#JsonProperty("name_bangla")
private String nameBangla;
#JsonProperty("name_english")
private String nameEnglish;
or changing how jackson (which is used for de/serialization) works:
Jackson has a setting called PropertyNamingStrategy.SNAKE_CASE
which you can set for the jackson objectmapper.
So, you need to configure Jackson for that, e.g. by adding your own object mapper:
#Configuration
public class JacksonConfiguration {
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
return new Jackson2ObjectMapperBuilder().propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)
}
}
As far as I know, in older version of JHipster, there was already a JacksonConfiguration to configure the JSR310 time module, but was removed later...
Adding this to your application.yml should also work:
spring.jackson.property-naming-strategy=SNAKE_CASE
Also you can use annotation to define naming strategy per class.
Little example in Kotlin:
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class)
data class Specialization(val altUrl: String, val altId: Int, val altName: String)
I tried to use the DataTime in my entity class. Adding #Temporal(TemporalType.DATE) above the field, I got the error saying "The persistent field or property for a Temporal type must be of type java.util.Date, java.util.Calendar or java.util.GregorianCalendar". I can introduce the conversion back and forth; using setters and getters as follows:
#Temporal(TemporalType.DATE)
private Calendar attendanceDate;
public DateTime getAttendanceDate() {
return new DateTime(this.attendanceDate);
}
public void setAttendanceDate(DateTime attendanceDate) {
this.attendanceDate = attendanceDate.toCalendar(Locale.getDefault());
}
but I hope eclipselink to do it for me. I have gone thro' Persist Joda-time's DateTime via Hibernate. The answer suggesting to use hibernate, but I have to use eclipselink. I can use the DateTime object in my entity class with DB representation as BLOB, but I need it as Date. Is there anything like jodatime-eclipselink? Or any other suggestion? Thanks for the help.
Basic the link defines an EclipseLink Converter to convert from Joda DateTime to java.sql.Timestamp or Date.
You could use it, or define your own converter and use it through #Convert, #Converter in EclipseLink.
For DDL creation, the converter should define the initialize method and set the type on the mapping's field to java.sql.Timestamp.
Please log a bug (or vote for the existing one) on this in EclipseLink, we should have support for Joda.
I Try use joda-time-eclipselink-integration, but don't work, problably I made something wrong,
So I made more researchs and i found this link http://jcodehelp.blogspot.com.br/2011/12/persist-joda-datetime-with-eclipselink.html, they use #Converter annotation to convert the Joda Date Time.
I Try and works for me, I hope, works for you too.
I wanted to do the same thing, and Googling around actually led me here. I was able to accomplish this using the #Access annotation. First, you annotate the class like this
#Access(AccessType.FIELD)
public class MyClass
{
....
This provides field access to everything so you don't have to annotate the fields individually. Then you create a getter and setter for the JPA to use.
#Column(name="my_date")
#Temporal(TemporalType.DATE)
#Access(AccessType.PROPERTY)
private Date getMyDateForDB()
{
return myDate.toDate();
}
private void setMyDateForDB(Date myDateFromDB)
{
myDate = new LocalDate(myDateFromDB);
}
The #Access(AccessType.PROPERTY) tells JPA to persist and retrieve through these methods.
Finally, you'll want to mark your member variable as transient as follows
#Transient
private LocalDate myDate = null;
This stops JPA from trying to persist from the field as well.
That should accomplish what you're trying to do. It worked great for me.
Ahamed, you mentioned it wasn't working for you. Additionally you need to override the initialize method of the converter to define the desired field type:
#Override
public void initialize(DatabaseMapping mapping, Session session) {
((AbstractDirectMapping) mapping)
.setFieldClassification(java.sql.Timestamp.class);
}
The following is a working example based on the answers available in the topic
Basically the easiest approach is to use EclipseLink #Converter for a DateTime field in your Entity.
The converter usage looks as follows:
import org.eclipse.persistence.annotations.Convert;
import org.eclipse.persistence.annotations.Converter;
import javax.persistence.*;
import org.joda.time.DateTime;
#Entity
public class YourEntity {
#Converter(name = "dateTimeConverter", converterClass = your.package.to.JodaDateTimeConverter.class)
#Convert("dateTimeConverter")
private DateTime date;
And the converter itself:
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.converters.Converter;
import org.eclipse.persistence.sessions.Session;
import org.joda.time.DateTime;
import java.sql.Timestamp;
public class JodaDateTimeConverter implements Converter {
private static final long serialVersionUID = 1L;
#Override
public Object convertDataValueToObjectValue(Object dataValue, Session session) {
return dataValue == null ? null : new DateTime(dataValue);
}
#Override
public Object convertObjectValueToDataValue(Object objectValue, Session session) {
return objectValue == null ? null : new Timestamp(((DateTime) objectValue).getMillis());
}
#Override
public void initialize(DatabaseMapping mapping, Session session) {
// this method can be empty if you are not creating DB from Entity classes
mapping.getField().setType(java.sql.Timestamp.class);
}
#Override
public boolean isMutable() {
return false;
}
}
I am adding this for the purpose of easy copy-and-paste solution.
Solution is here
joda-time-eclipselink-integration
Answer from Atais works well. Below an upgrade to it.
You can omit #converter annotation by registering it globally.
At persistance.xml in persitence-unit add:
<mapping-file>META-INF/xyz-orm.xml</mapping-file>
and file META-INF/xyz-orm.xml with content:
<?xml version="1.0"?>
<entity-mappings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/orm" version="2.1">
<converter class="pl.ds.wsc.storage.converter.JodaDateTimeConverter"/>
</entity-mappings>
If your config file is META-INF/orm.xml then you can omit even first step because it is default confing for all persitence units.