Spring Boot Open Api Request LocalTime - spring-boot

I use Spring Boot 2.4.5, springdoc-openapi-ui 1.5.7,
My Entity has fields:
private LocalDate beginDate;
private LocalTime beginTime;
Request body
This option does not work
{
"beginDate": "2021-04-25",
"beginTime": {
"hour": 0,
"minute": 0,
"second": 0,
"nano": 0
}
And this worker
{
"beginDate": "2021-04-25",
"beginTime": "00:00:00"
}
I tried various field annotations and adding dependencies, but I got a 400 or 500 error.
I see two possible solutions:
configure the schema display in Swagger " 00:00:00"
properly process the json with the painted components
Thank you in advance for your help!

Thats because you need to define a deserializer so, that your request can be properly handled when passing 4 fields (hour/min/sec/nano) instead of just a single String field.
#JsonDeserialize(using = LocalTimeDeserializer.class)
private LocalTime beginTime;
you may also want to consider using this feature on your ObjectMapper when writing your deserialization class.
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());

Related

POJO to Entity mapping with ModelMapper

My POJO has a variable annotated like this
#JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSxxx")
#JsonDeserialize(using = ZonedDateTimeDeserializer.class)
private ZonedDateTime startTs;
//other private variables
My goal is to map this to an Entity for which I'm using ModelMapper and the sample code goes like this
public POJOEntity mapToPOJOEntity(POJO pojo) {
return modelMapper.map(pojo, POJOEntity.class);
}
After mapping when I look at the startTs variable in debug mode it has the desired format but when I check in the db startTs is saving as a whole ZonedDateTime object rather than the desired format I specified in POJO. Did anyone face this issue if yes can someone please help?
Value saved in cosmosDB:
Expected output: "startTs": "2023-01-12T08:58:32.452-06:00"
PS: CosmosDB, spring-boot, Java8, ModelMapper version: 2.3.8

Converting java.sql.timestamp to millisecond format

I have a private java.sql.Timestamp myDate in some model (POJO) class like below
private String isActive;
private Date dob;
private Timestamp createdDate;
private Timestamp lastMktPrefUpdateAt;
We were using spring boot version to 1.5.10.RELEASE and REST API response for timestamp field was in millisecond format like below -
{
"isActive": "y",
"lastMktPrefUpdateAt": 1632195609787,
"dob": "08/12/1991",
"createdDate": 1632195609788
}
We have recently upgraded the spring boot version to 2.3.0.RELEASE and this started sending timestamp field in response to some ISO format like below -
{
"isActive": "y",
"lastMktPrefUpdateAt": "2021-09-20T22:10:09.787+0000",
"dob": "07/12/1991",
"createdDate": "2021-09-20T22:10:09.788+0000"
}
Can someone please help answering, how can we format Timestamp back to millisecond format?
We need to format Timestamp at global level meaning without changing all the POJO classes.
Try this in your properties file
spring.jackson.serialization.write-dates-as-timestamps:true
OR
Use #JsonFormat(shape = JsonFormat.Shape.NUMBER) above the property that you want.
Update
If you want to add millis to your timestamp, try to pass the long value to the Timestamp constructor and call the getTime() method to receive a 13-digits timestamp.
long now = System.currentTimeMillis();
Timestamp timeStamp= new Timestamp(now);
System.out.println(timeStamp.getTime());
You could convert that string to a ZonedDateTime object which has a .toInstant().toEpochMilli() method.
Something like:
long millis = ZonedDateTime.of(LocalDateTime.parse(str), ZoneId.of("Etc/UTC")).toInstant().toEpochMilli();
see:
How to get milliseconds from LocalDateTime in Java 8
https://docs.oracle.com/javase/8/docs/api/java/time/LocalDateTime.html
https://docs.oracle.com/javase/8/docs/api/java/time/ZonedDateTime.html
However, I would recommend refactoring the system to use the immutable LocalDatTime object at a later point.
(There may be an appropriate annotation you can put on that timestamp field to parse it in a specific way, but not one that I am aware of.)

Spring Data Elasticsearch Repository query define date input parameter format

I am using elasticsearch 6.5.3 and Spring Boot 2.1.6 and spring-data-elasticsearch 3.2.0.M1.
I have defined the Elasticsearch configuration as:
#Bean
public ElasticsearchOperations elasticsearchTemplate() {
return new ElasticsearchRestTemplate(client(), new CustomEntityMapper());
}
public static class CustomEntityMapper implements EntityMapper {
private final ObjectMapper objectMapper;
public CustomEntityMapper() {
//we use this so that Elasticsearch understands LocalDate and LocalDateTime objects
objectMapper = new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
//MUST be registered BEFORE calling findAndRegisterModules
.registerModule(new JavaTimeModule())
.registerModule(new Jdk8Module());
//only autodetect fields and ignore getters and setters for nonexistent fields when serializing/deserializing
objectMapper.setVisibility(objectMapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
//load the other available modules as well
objectMapper.findAndRegisterModules();
}
#Override
public String mapToString(Object object) throws IOException {
return objectMapper.writeValueAsString(object);
}
#Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return objectMapper.readValue(source, clazz);
}
}
I have a repository with a method defined as:
List<AccountDateRollSchedule> findAllByNextRollDateTimeLessThanEqual(final LocalDateTime dateTime);
And the POJO AccountDateRollSchedule defines that field as:
#Field(type = FieldType.Date, format = DateFormat.date_hour_minute)
#DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm")
private LocalDateTime nextRollDateTime;
I see my index properly has that field created as declared and expected:
"nextRollDateTime": {
"type": "date",
"format": "date_hour_minute"
}
Also querying the index returns the field formatted as expected:
"nextRollDateTime" : "2019-06-27T13:34"
My repository query would translate to:
{"query":
{"bool" :
{"must" :
{"range" :
{"nextRollDateTime" :
{"from" : null,
"to" : "?0",
"include_lower" : true,
"include_upper" : true
}
}
}
}
}
}
But passing any LocalDateTime input to the method does NOT respect the format defined for the field, the FULL format is always used instead. Invoking:
findAllByNextRollDateTimeLessThanEqual(LocalDateTime.now(ZoneOffset.UTC).truncatedTo(ChronoUnit.MINUTES));
gives me the following exception (any #DateTimeFormat or #JsonFormat annotation on the method parameter in the repository is ignored):
Unrecognized chars at the end of [2019-07-22T09:07:00.000]: [:00.000]
If I instead change the repository method to accept a String and pass a String formatted exactly as expected as input to it, it works no problem.
Is it possible to somehow define the format used for the date parameter passed in input to the repository method or have Spring use the one configured on the field itself?
I would like not to wrap that method for a simple conversion like this (I did and it works), and I would also like to avoid using long type for the date field
Thanks and cheers
For reference, I also open issue on Spring JIRA
These problems are one reason why we move away from using and exposing the JacksonMapper in Spring Data Elasticsearch. From version 4.0 on all you need on your property is the one annotation:
#Field(type = FieldType.Date, format = DateFormat.date_hour_minute)
private LocalDateTime nextRollDateTime;
This will then be used in writing the index mappings, when entities are indexed and retrieved, and also when repository method and queries are processed.
But for the 3.2.x version you will have to use a workaround like the wrapping you mentioned.

How to remove some fields of an Object in Spring Boot response control?

this is one of my REST controller,
#RestController
#RequestMapping("/users/Ache")
public class Users {
#GetMapping
public User getUser() {
User user = new User();
return user;
}
}
As response, Spring boot will translate my Object to JSON,
this is response:
{
"username": "Ache",
"password": "eee",
"token": "W0wpuLAUQCwIH1r2ab85gWdJOiy2cp",
"email": null,
"birthday": null,
"createDatetime": "2019-03-15T01:39:11.000+0000",
"updateDatetime": null,
"phoneNumber": null
}
I want to remove password and token fields, How can I do?
I know two hard ways:
create a new hash map
and add some necessary fields, but it too complex
set those two fields to null
but it still leaves two null valued fields, it is too ugly.
Any better solution?
Spring leverages Jackson library for JSON marshalling by default. The easiest solution that comes to mind is making use of Jackson's #JsonIgnore but that would ignore the property on both serialization and de-serialization. So the right approach would be annotating the field with #JsonProperty(access = Access.WRITE_ONLY).
For instance, inside a hypothetical User class:
#JsonProperty(access = Access.WRITE_ONLY)
private String password;
#JsonProperty(access = Access.WRITE_ONLY)
private String token;
An alternative would be using #JsonIgnore only on the getter:
#JsonIgnore
public String getPassword() {
return this.password;
}
You can also create another class, for instance UserResponse with all the fields except password and token, and make it your return type. Of course it involves creating an object and populating it, but you leave your User class clean without Jackson annotations, and de-couples your model from your representation.
Keep the getter and setter but add the WRITE_ONLY JsonProperty. This way password validations will work when you use the entity as the request body.
#NotBlank
#JsonProperty(access = Access.WRITE_ONLY)
private String password;

Return Client Specific Response from the Same REST Endpoint in Spring 4

I have a following REST end-point in my Spring Boot Application.
/employees
This returns following response.
{
"id": 1,
"firstName": "John",
"lastName": "Doe",
"department": "IT",
"salary": "$5000"
...
//There are more here
}
Now, I need to have this end-point used by two different clients. But the need to know only part information from this end-point.
So for client1, the output would be as below:
{
"id" : 1,
"firstName" : "John",
"lastName" : "Doe"
}
And for the other client(client2), the response should be.
{
"id" : 1,
"department": "IT",
"salary": "$5000"
}
How can I approach this problem is best possible way?
Client1 and Client2 would be distinguished by their authentication details. Maybe a configuration which specifies how the response entity needs to be filtered. That way, whenever I am configuring a new client, I can create a new configuration for the client and that takes care of the response filtering.
you can autowire HttpServletRequest object , in the controller class, and then use it to get client details like header or user-agent information.
Then these info can be used to check the client type and accordingly response date can be build and send.
You can do it like:
Client - 1:
ResponseEntity<ObjectType1> result = restTemplate.exchange(url, HttpMethod.POST, ObjectType1.class);
Client - 2:
ResponseEntity<ObjectType2> result = restTemplate.exchange(url, HttpMethod.POST, ObjectType2.class);
Where your custom objects should look like so
public class ObjectType1{
int id;
String firstName;
String lastName;
//default Constructor & getter & setters
}
public class ObjectType1{
int id;
String department;
String salary;
//default Constructor & getter & setters
}
In above cases, spring/ object mapper will automatically map the returned response/parameters with your required object parameters(objectType1 or objectType2)
You should investigate json views feature of Jackson. You can use the same object but filter the fields based on views. You can find some more details at this question

Resources