Passing date as json object through Spring Hibernate - java

I am using Spring Hibernate framework. And I have a problem in passing date as json object. Whenever I try to insert an object, it says error 400, request syntactically incorrect.
My controller class
#RequestMapping(value="/hospital", method= RequestMethod.POST,
consumes=MediaType.APPLICATION_JSON_VALUE,produces=MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody Status addHospitalInfo(#RequestBody HospitalInformation hospitalInformation){
try{
if(hospitalService.addHospitalInfo(hospitalInformation)){
return new Status(1,"Success");
}else{
return new Status(0,"Failed");
}
}catch(Exception e){
e.printStackTrace();
return new Status(0,e.getMessage());
}
}
My domain class is
private Integer hospitalId;
private String shortName;
private String name;
private Integer packageId;
private Date implementationDate;
private Date validFrom;
private Date validUpTo;
public enum SubscriptionType{Free,Complimentary,Paid}
private Integer totalUsers;
private Package packages;
public enum Status{Active,Inactive}
private SubscriptionType subscriptionType;
private Status status;
//normal getters and setters for other fields
#Column(name = "implementation_date",
nullable = false)
public Date getImplementationDate() {
return implementationDate;
}
public void setImplementationDate(Date implementationDate)
{
this.implementationDate = implementationDate;
}
#Column(name = "valid_from",
nullable = false)
public Date getValidFrom() {
return validFrom;
}
public void setValidFrom(Date validFrom)
{
this.validFrom =validFrom;
}
#Column(name = "valid_upto",
nullable = false)
public Date getValidUpTo() {
return validUpTo;
}
public void setValidUpTo(Date validUpTo)
{
this.validUpTo =validUpTo;
}
My Dao is
#Transactional
public boolean addHospitalInfo(HospitalInformation hospitalInformation)
throws Exception {
Session session=sessionFactory.openSession();
Transaction tx=session.beginTransaction();
if(findByPackageId(hospitalInformation.getPackageId())== null){
return false;
}
else{
session.save(hospitalInformation);
tx.commit();
session.close();
return true;
}
}
#Transactional
public Package findByPackageId(Integer packageId) throws Exception {
Session session=sessionFactory.openSession();
Transaction tx= session.beginTransaction();
List<Package> package1= new ArrayList<Package>();
package1=session
.createQuery("from Package where packageId=?")
.setParameter(0, packageId)
.list();
if (package1.size() > 0) {
return package1.get(0);
} else {
return null;
}
}
And my service class just saves the object into database. So I need help on how to pass date as json object. Thankyou in advance.

To fix your issue you can do one of the two things:
Either use a format that Jackson already recognizes ("yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "EEE, dd MMM yyyy HH:mm:ss zzz", "yyyy-MM-dd")
or
write a custom deserializer e.g. for yyyyMMdd
public class YourDateDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
String date = jp.getText();
try {
return format.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
and annotate your date fields like
#JsonDeserialize(using = YourDateDeserializer.class)
private Date implementationDate;
#JsonDeserialize(using = YourDateDeserializer.class)
private Date validFrom;
#JsonDeserialize(using = YourDateDeserializer.class)
private Date validUpTo;
Serialization
To have your dates printed the way you want in your JSON response, you can write a custom JSON serializer and annotate the fileds with it, so for yyyyMMdd something like
public class YourDateSerializer extends JsonSerializer<Date> {
#Override
public void serialize(Date value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,JsonProcessingException {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
jgen.writeString(format.format(value));
}
#Override
public Class<Date> handledType() {
return Date.class;
}
}
and than annotate your field, e.g.
#JsonDeserialize(using = YourDateSerializer.class)
#JsonDeserialize(using = YourDateDeserializer.class)
private Date implementationDate;
Global config
Note also that you can configure your custom serializers to take effect globally, by customizing Jackson's ObjectMapper that is in charge for the conversion. So something like
#Component
public class CustomObjectMapper extends ObjectMapper {
public CustomObjectMapper() {
SimpleModule module = new SimpleModule("JsonDateModule", new Version(2, 0, 0, null, null, null));
module.addSerializer(Date.class, new YourDateSerializer());
module.addDeserializer(Date.class, new YourDateDeserializer());
registerModule(module);
}
}
you would need to register your CustomObjectMapper with spring. If you're using the XML config, it would be something like
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="your.package.CustomObjectMapper"/>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>

Related

Unable to Deserialize - Jackson LocalDate/Time - JUnit

I need to deserialize the Json to Java Objects in Junit. I have Json file like
{
"studentId":57,
"JoinedDate":"31-12-2019",
"DOB":"08-06-1998"
}
I have class for the same to map
public class Student{
private long studentId ;
private LocalDate JoinedDate;
private LocalDate DOB ;
public long getStudentId() {
return studentId;
}
public void setStudentId(long studentId) {
this.studentId = studentId;
}
public LocalDate getJoinedDate() {
return JoinedDate;
}
public void setJoinedDate(LocalDate joinedDate) {
JoinedDate = joinedDate;
}
public LocalDate getDOB() {
return DOB;
}
public void setDOB(LocalDate dOB) {
DOB = dOB;
}
I need to write centralized builder for Unit testing project similar like this
builder.deserializers(new LocalDateDeserializer(DateTimeFormatter.ofPattern(dateFormat)));
builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(dateFormat)));
Main Class
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Main.class)
#WebAppConfiguration
public class Main{
#Test
public void contextLoads() {
assertTrue(true);
}
}
Unit testing Project looks like
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Main.class)
#WebAppConfiguration
public class StudentTest{
private ObjectMapper jsonObjectMapper;
#Before
public void setUp() throws IOException {
jsonObjectMapper = new ObjectMapper();
studentJson = IOUtils.toString(getClass().getResourceAsStream(CommonTestConstants.StudentPath+ "/Student.json"));
}
I'm getting a error while mapping the objects -
com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type java.time.LocalDate from String "31-12-2019": Failed to deserialize java.time.LocalDate:
Another Error - Sometimes.
com.fasterxml.jackson.databind.JsonMappingException: Text '31-12-2019'
could not be parsed at index 0
I assume LocalDate format mismatch is the issue. Any suggestion to make it centralized way instead of specifying the format above the fields. Any one please advise?
Reference - Spring Boot JacksonTester custom serializer not registered
You just need to specify the date format by default jackson allows format of yyyy-MM-dd
public class Student{
private long studentId ;
#JsonProperty("JoinedDate") #JsonFormat(pattern = "dd/MM/yyyy")
private LocalDate JoinedDate;
#JsonProperty("DOB") #JsonFormat(pattern = "dd/MM/yyyy")
private LocalDate DOB ;
public long getStudentId() {
return studentId;
}
public void setStudentId(long studentId) {
this.studentId = studentId;
}
public LocalDate getJoinedDate() {
return JoinedDate;
}
public void setJoinedDate(LocalDate joinedDate) {
this.JoinedDate = joinedDate;
}
public LocalDate getDOB() {
return DOB;
}
public void setDOB(LocalDate dOB) {
this.DOB = dOB;
}
I hope it helps you
Springboot 1.4.x or above has this interface Jackson2ObjectMapperBuilderCustomizer which allows you to initialize objectMapper.
What we need to do, is override customize method and register deserializers and
serializers.
#SpringBootApplication
public class TestApplication implements Jackson2ObjectMapperBuilderCustomizer {
#Override
public void customize(Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) {
// pattern could be anything whatever is required
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/dd/MM");
LocalDateSerializer localDateDeserializer = new LocalDateSerializer(formatter);
jackson2ObjectMapperBuilder
.failOnEmptyBeans(false)
.deserializersByType(new HashMap<Class<?>, JsonDeserializer<?>>(){{
put(LocalTime.class, localTimeSerializer);
}});
}
}
We can also add seriliazers similar way.
jackson2ObjectMapperBuilder
.failOnEmptyBeans(false)
.serializersByType(new HashMap<Class<?>, JsonSerializer<?>>(){{
put(LocalTime.class, localTimeSerializer);
}});
you can check more details here. Spring Jackson builder

Format date in response JSON from controller

I am returning a JSON as a response from the controller. I want to format the date fields in this response.
Controller-
#RequestMapping(value = "/call", method = RequestMethod.GET)
public SampleDTO get()
{
......
return sampleDTO;
}
SampleDTO-
{
"date" : "2020-03-10T08:57:58+0000",
"text" : "abc"
}
I want to format the date field to dd-MM-yyyy
To do this I add the #JsonFormat annotation to the bean class of SampleDTO.
SampleDTO.java -
import java.util.Date;
public class SampleDTO
{
#JsonFormat(pattern = "dd-MM-yyyy")
private Date date;
private String text;
#JsonFormat(pattern = "dd-MM-yyyy")
public void setDate(final Date date)
{
this.date = date;
}
#JsonFormat(pattern = "dd-MM-yyyy")
public Date getDate()
{
return date;
}
public void setText(final String text)
{
this.text = text;
}
public String getText()
{
return text;
}
}
Still, I am getting this format in the response on my browser.
"date" : "2020-03-10T08:57:58+0000"
EDIT 1:
Instead of returning the sampleDTO, converting it to String directly in the code works perfectly fine.
This works like a charm:
SampleDTO sampleDTO = new SampleDTO();
sampleDTO.setCreated(new Date());
ObjectMapper om = new ObjectMapper();
return om.writeValueAsString(sampleDTO);
Please, check that your Date is from java.util and not from java.sql package. Plus try the following:
#JsonSerialize(as = Date.class)
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="dd-MM-yyyy")
Could you try this on the field level and remove from getDate() method in your DTO.
Something like this,
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
private Date date;
This should work with your current version of jackson-databind:2.9.8.jar.
Here is the small example for you:
public class ExampleMain {
public static void main(String[] args) throws IOException {
Employee employee = new Employee();
employee.setDateOfBirth(Date.from(ZonedDateTime.now().minusYears(30).toInstant()));
System.out.println("-- before serialization --");
System.out.println(employee);
System.out.println("-- after serialization --");
ObjectMapper om = new ObjectMapper();
String jsonString = om.writeValueAsString(employee);
System.out.println(jsonString);
System.out.println("-- after deserialization --");
System.out.println(om.readValue(jsonString, Employee.class));
}
}
public class Employee {
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
private Date dateOfBirth;
public Date getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(Date dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
#Override
public String toString() {
return "Employee{" +
", dateOfBirth=" + dateOfBirth +
'}';
}
}
There are three levels of how you can solve this date format issue with Spring.
1) Using #JsonFormat on your date fields
In this case, you need to use the same annotation in front of all your private date members.
public class MyBean{
#JsonFormat(pattern="yyyy-MM-dd")
private Date birthday;
#JsonFormat(pattern="yyyy-MM-dd")
private LocalDate birthday;
// getters and setters here
}
2) Setting the Default format
If you want to configure the default date format for all dates in your application, add the following line to the application.properties or application.yml config file:
spring.jackson.date-format=yyyy-MM-dd
Unfortunately, this solution doesn't work with the Java 8 date types, like LocalDate and LocalDateTime.
3) Customizing your Jackson ObjectMapper
This solution works like a charm with Java 8 date types as well.
#Configuration
public class ContactAppConfig {
private static final String DATE_FORMAT = "yyyy-MM-dd";
private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
#Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> {
builder.simpleDateFormat(DATE_TIME_FORMAT);
builder.serializers(new LocalDateSerializer(DateTimeFormatter.ofPattern(DATE_FORMAT)));
builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT)));
};
}
}
I suggest you use the 3rd option.
you can use jstl format to format the date :)
<%# taglib prefix = "fmt" uri = "http://java.sun.com/jsp/jstl/fmt" %>
<fmt:formatDate pattern = "yyyy-MM-dd" value = "${date}" />

Spring Boot - How to create a custom serialization for " java.time.LocalDateTime" class

I have developed a rest service with Spring Boot. I want return a json response with the birthday of the user as milliseconds.
How to serialize a java.time.LocalDateTime object to milliseconds ?
My model class:
#Entity(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue
#Column(name = "user_id")
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "date_of_birth")
private LocalDateTime dateOfBirth;
. . .
}
current response:
{
. . .
"dateOfBirth":[2018,7,25,7,0],
. . .
}
preferred response:
{
. . .
"dateOfBirth": 1532786354419,
. . .
}
use #JsonSerialize(using = CustomSerializer.class)
#Entity(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue
#Column(name = "user_id")
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#JsonSerialize(using = CustomSerializer.class)
#Column(name = "date_of_birth")
private LocalDateTime dateOfBirth;
. . .
}
custom serilizer class:
public class CustomSerializer extends JsonSerializer<LocalDateTime> {
#Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
//add your custom date parser
gen.writeString(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()+"");
}
}
If you don't want to decorate all you objects with #JsonSerialize you can configure the object mapper to always return a long for LocalDateTime.
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateSerializer());
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateDeserializer());
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
and the de- and serializers.
public class LocalDateSerializer extends JsonSerializer<LocalDateTime> {
#Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(String.valueOf(value.atZone(ZoneId.systemDefault()).toEpochSecond()));
}
}
public class LocalDateDeserializer extends JsonDeserializer<LocalDateTime> {
#Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return LocalDateTime.ofInstant(Instant.ofEpochSecond(Long.parseLong(p.getValueAsString())), ZoneId.systemDefault());
}
}
In your example you are using date of birth which could be a LocalDate.
Why dont you store date property as below :
#Temporal(TemporalType.TIMESTAMP)
private Date date = new Date();
It gives date in milliseconds. Formatting it to desired output is responsibility of application layer. Don't do it on entity itself with some annotation.

Java 303 / 349 start date before end date validation

Is there a better way of writing a Java validator which ensures that a start date is before an end date than writing a class level ConstraintValidator in the following manner:
// VALIDATOR IMPLEMENTATION
public class StartBeforeEndDateValidator implements ConstraintValidator<StartBeforeEndDateValid, Object> {
// cannot use LocalDate here...
private String start;
private String end;
#Override
public void initialize(final StartBeforeEndDateValid annotation) {
start = annotation.start();
end = annotation.end();
}
#Override
public boolean isValid(final Object bean, final ConstraintValidatorContext context) {
try {
final String startDateStr = BeanUtils.getProperty(bean, start);
final String endDateStr = BeanUtils.getProperty(bean, end);
final LocalDate startDate = new LocalDate(startDateStr);
final LocalDate endDate = new LocalDate(endDateStr);
return !startDate.isAfter(endDate);
} catch (final Exception e) {
return false;
}
}
}
// USAGE
#StartBeforeEndDateValid(start = "startDate", end = "endDate")
#Entity
public class MyBean {
#NotNull
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
private LocalDate startDate;
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
private LocalDate endDate;
...
}
I don't really like the fact that I have to use reflection to extract the 2 date objects from the bean. Unfortunately afaik the validation spec does not specify a way to set only the values you want to validate from the bean.
One way would be to add an interface to MyBean
public interface StartEndDateable {
public LocalDate getStartDate();
public LocalDate getEndDate();
}
public class MyBean implements StartEndDatable {
...
Then you can set the generic type on ConstraintValidator to the new interface instead of Object.
public class StartBeforeEndDateValidator implements ConstraintValidator<StartBeforeEndDateValid, StartEndDatable> {
#Override
public void initialize(StartBeforeEndDateValid annotation) {
}
#Override
public boolean isValid(StartEndDatable bean, ConstraintValidatorContext context) {
final LocalDate startDate = bean.getStartDate();
final LocalDate endDate = bean.getEndDate();
return !startDate.isAfter(endDate);
}
}
Obviously any class you then want to validate with the start and end date will have to implement the StartEndDateable (Not the best name, I know, but I'm sure you can think of something better) and define the getStartDate and getEndDate methods.

Stop Jackson custom Date Serializer writing nulls

I'm using Jackson to handle JSON, I have a custom date serializer for formatting dates in the way that I want but it doesn't abide the #JsonSerialize(include = Inclusion.NON_NULL) annotations. The serializer is below.
If the date is null it still gets written. If I don't use the custom serializer all is fine, null values don't get written. My question is, is there something in the JsonSerializer class that needs to be done to stop null values being written?
public class DateSerializer extends JsonSerializer<Date>
{
#Override
public final void serialize(Date date, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException
{
SimpleDateFormat sdf = new SimpleDateFormat(MY_FORMAT);
jgen.writeString(sdf.format(date));
}
}
Turns out that it wasn't the serializer at all, but the #JsonSerialize annotation that defaults to include=ALWAYS which overrides the include=NON_NULL on the class. So changing the annotation of the getter works:
changed:
#JsonSerialize(using = DateSerializer.class)
public Date getDate()
{
return date;
}
to:
#JsonSerialize(using = DateSerializer.class,
include=JsonSerialize.Inclusion.NON_NULL)
public Date getDate()
{
return date;
}
Seems that from 2.1.4 to 2.4.2 this behaviour has been fixed and a general
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(Include.NON_NULL);
does the trick now.
You can crete a Serializaer/Deserializer for your Custom Date format.
For the Deserializer:
public class JsonDateDeserializer extends JsonDeserializer<Date> {
private static final SimpleDateFormat dateFormat;
static {
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
dateFormat.setTimeZone(SimpleTimeZone.getTimeZone("UTC"));
}
#Override
public Date deserialize(JsonParser jsonparser, DeserializationContext deserializationcontext) throws IOException {
String date = jsonparser.getText();
if (date != null && !"".equals(date)) {
try {
return dateFormat.parse(date);
} catch (ParseException e) {
throw new JsonParseException(jsonparser, e.getMessage());
}
}
return null;
}
}
For the Serializer:
public class JsonDateSerializer extends JsonSerializer<Date> {
private static final SimpleDateFormat dateFormat;
static {
dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss'Z'");
dateFormat.setTimeZone(SimpleTimeZone.getTimeZone("UTC"));
}
#Override
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider) throws IOException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}
And in your POJO:
#Data
public class Person implements Serializable {
private static final long serialVersionUID = -8041031461422721556L;
private Long id;
private String name;
#JsonSerialize(using = JsonDateSerializer.class)
#JsonDeserialize(using = JsonDateDeserializer.class)
private Date dob;
}

Categories

Resources